Update gupnp to 0.20.5 (fdeb6f9f)
[profile/ivi/GUPnP.git] / tools / gupnp-binding-tool
1 #! /usr/bin/python
2
3 # Copyright (C) 2008 OpenedHand Ltd
4 # Copyright (C) 2008 Intel Corporation
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free Software
8 # Foundation; either version 2, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
17 # St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 # TODO:
20 # - finish code cleanup
21 # - currently allowedValueList is not used: could use it to turn 
22 #   current char* value to an enum
23 # - could warn if values outside allowedValueRange are used in *_action_set() 
24 # - add option to generate skeleton source code that uses the bindings? 
25
26 import os.path, re, xml.etree.ElementTree as ET
27 from optparse import OptionParser
28
29
30 # upnp type:     (C type,      GType,                     type for g_value_get_* 
31 #                                                         and g_value_set_*)
32 typemap = {
33   'ui1':         ('guint ',    'G_TYPE_UINT',             'uint'),
34   'ui2':         ('guint ',    'G_TYPE_UINT',             'uint'),
35   'ui4':         ('guint ',    'G_TYPE_UINT',             'uint'),
36   'i1':          ('gint' ,     'G_TYPE_INT',              'int'),
37   'i2':          ('gint ',     'G_TYPE_INT',              'int'),
38   'i4':          ('gint ',     'G_TYPE_INT',              'int'),
39   'int':         ('gint ',     'G_TYPE_INT',              'int'),
40   'r4':          ('gfloat ',   'G_TYPE_FLOAT',            'float'),
41   'r8':          ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
42   'number':      ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
43   'fixed.14.4':  ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
44   'float':       ('gdouble ',  'G_TYPE_DOUBLE',           'double'),
45   'char':        ('gchar ',    'G_TYPE_CHAR',             'char'),
46   'string':      ('gchar *',   'G_TYPE_STRING',           'string'),
47   'date':        ('gchar *',   'GUPNP_TYPE_DATE',         'string'),
48   'dateTime':    ('gchar *',   'GUPNP_TYPE_DATE_TIME',    'string'),
49   'dateTime.tz': ('gchar *',   'GUPNP_TYPE_DATE_TIME_TZ', 'string'),
50   'time':        ('gchar *',   'GUPNP_TYPE_TIME',         'string'),
51   'time.tz':     ('gchar *',   'GUPNP_TYPE_TIME_TZ',      'string'),
52   'boolean':     ('gboolean ', 'G_TYPE_BOOLEAN',          'boolean'),
53   'bin.base64':  ('gchar *',   'GUPNP_TYPE_BIN_BASE64',   'string'),
54   'bin.hex':     ('gchar *',   'GUPNP_TYPE_BIN_HEX',      'string'),
55   'uri':         ('gchar *',   'GUPNP_TYPE_URI',          'string'),
56   'uuid':        ('gchar *',   'GUPNP_TYPE_UUID',         'string')
57 }
58
59
60 class Action:
61     def __init__(self):
62         self.name = None
63         self.c_name = None
64         self.c_prefixed_name = None
65         self.in_args = []
66         self.out_args = []
67         self.notify_vars = []
68
69
70 class Argument:
71     def __init__(self):
72         self.name = None
73         self.c_name = None
74         self.direction = None
75         self.related_var = None
76
77
78 class Variable:
79     def __init__(self):
80         self.name = None
81         self.c_name = None
82         self.c_prefixed_name = None
83         self.ctype = None
84         self.gtype = None
85         self.get_function = None
86         self.set_function = None
87         self.notified = True
88         self.dummy = False
89
90
91 def camelCaseToLowerCase (str):
92     # http://www.djangosnippets.org/snippets/585/
93     tmp = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str)
94     lower_case = tmp.lower().strip('_')
95     return re.sub('[^a-z0-9]', '_', lower_case)
96
97
98 def getActions(action_elements, prefix, variables):
99     """
100     Parse the action element list into a list of Action objects.
101     """
102     
103     actions = []
104     
105     for actionElement in action_elements:
106         a = Action()
107         actions.append(a)
108         a.name = actionElement.find("{urn:schemas-upnp-org:service-1-0}name").text
109         
110         if a.name is None:
111             raise Exception("No name found for action")
112         a.c_name = camelCaseToLowerCase (a.name)
113         a.c_prefixed_name = prefix + a.c_name
114         
115         for argElement in actionElement.findall("{urn:schemas-upnp-org:service-1-0}argumentList/{urn:schemas-upnp-org:service-1-0}argument"):
116             arg = Argument()
117
118             arg.name = argElement.find("{urn:schemas-upnp-org:service-1-0}name").text
119             if arg.name is None:
120                 raise Exception("No name found for argument")
121             arg.c_name = camelCaseToLowerCase (arg.name)
122             
123             var_name = argElement.find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable").text
124             for var in variables:
125                 if var.name == var_name:
126                     arg.related_var = var
127                     break
128             if arg.related_var is None:
129                 raise Exception("%s: related state variable %s not found" % (arg.name, var_name))
130             
131             arg.direction = argElement.find("{urn:schemas-upnp-org:service-1-0}direction").text
132             
133             if arg.direction == "in":
134                     a.in_args.append(arg)
135             else:
136                     a.out_args.append(arg)
137     
138     return actions
139
140
141 def getVariables(var_elements, prefix):
142     """
143     Parse the state variable element list into a list of Variable objects.
144     """
145     
146     variables = []
147
148     for varElement in var_elements:
149         var = Variable()
150         variables.append(var)
151         
152         var.name = varElement.find("{urn:schemas-upnp-org:service-1-0}name").text
153         if var.name.startswith("A_ARG_TYPE_"):
154             # dummy state variable, only there to give type info to getter/setter
155             var.dummy = True
156         
157         var.c_name = camelCaseToLowerCase (var.name)
158         var.c_prefixed_name = prefix + var.c_name
159         
160         if (varElement.get("sendEvents") == "no"):
161             var.notified = False
162         
163         dataType = varElement.find("{urn:schemas-upnp-org:service-1-0}dataType").text
164         if not dataType in typemap:
165             raise Exception("Unknown dataType %s" % dataType)
166         (var.ctype, var.gtype, g_value_type) = typemap[dataType];
167         var.get_function = "g_value_get_" + g_value_type
168         var.set_function = "g_value_set_" + g_value_type
169     
170     return variables
171
172
173 def printClientSyncActionBinding(a):
174     indent = (2 + len (a.c_prefixed_name)) * " "
175
176     print("static inline gboolean")
177     print("%s (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
178     
179     for arg in a.in_args:
180         print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
181         
182     for arg in a.out_args:
183         print("%s%s*out_%s," % (indent, arg.related_var.ctype, arg.c_name))
184         
185     print("%sGError **error)" % indent)
186     
187     print("{")
188
189     print("  return gupnp_service_proxy_send_action")
190     print("    (proxy, \"%s\", error," % a.name)
191     
192     for arg in a.in_args:
193         print("     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
194     print("     NULL,")
195     
196     for arg in a.out_args:
197         print("     \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
198     print("     NULL);")
199     
200     print("}\n")
201
202
203 def printClientAsyncActionBinding(a):
204     # User-callback prototype
205     indent = (24 + len (a.c_prefixed_name)) * " "
206     print("typedef void (*%s_reply) (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
207     for arg in a.out_args:
208         print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
209     print("%sGError *error," % indent)
210     print("%sgpointer userdata);" % indent)
211     print("")
212
213     # Generated async callback handler, calls user-provided callback
214     indent = (30 + len (a.c_prefixed_name)) * " "
215     print("static void _%s_async_callback (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
216     print("%sGUPnPServiceProxyAction *action," % indent)
217     print("%sgpointer user_data)" % indent)
218     print("{")
219     print("  GUPnPAsyncData *cbdata;")
220     print("  GError *error = NULL;")
221     for arg in a.out_args:
222         print("  %s%s;" % (arg.related_var.ctype, arg.c_name))
223     print("")
224     print("  cbdata = (GUPnPAsyncData *) user_data;")
225     print("  gupnp_service_proxy_end_action")
226     print("    (proxy, action, &error,")
227     for arg in a.out_args:
228         print("     \"%s\", %s, &%s," % (arg.name, arg.related_var.gtype, arg.c_name))
229     print("     NULL);")
230     print("  ((%s_reply)cbdata->cb)" % a.c_prefixed_name)
231     print("    (proxy,")
232     for arg in a.out_args:
233         print("     %s," % arg.c_name)
234     print("     error, cbdata->userdata);")
235     print("")
236     print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
237     print("}")
238     print("")
239
240     # Entry point
241     indent = (8 + len (a.c_prefixed_name)) * " "
242     print("static inline GUPnPServiceProxyAction *")
243     print("%s_async (GUPnPServiceProxy *proxy," % a.c_prefixed_name)
244     for arg in a.in_args:
245         print("%sconst %sin_%s," % (indent, arg.related_var.ctype, arg.c_name))
246     print("%s%s_reply callback," % (indent, a.c_prefixed_name))
247     print("%sgpointer userdata)" % indent)
248     print("{")
249     print("  GUPnPServiceProxyAction* action;")
250     print("  GUPnPAsyncData *cbdata;")
251     print("")
252     print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
253     print("  cbdata->cb = G_CALLBACK (callback);")
254     print("  cbdata->userdata = userdata;")
255     print("  action = gupnp_service_proxy_begin_action")
256     print("    (proxy, \"%s\"," % a.name)
257     print("     _%s_async_callback, cbdata," % a.c_prefixed_name)
258     for arg in a.in_args:
259         print("     \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
260     print("     NULL);")
261     print("")
262     print("  return action;")
263     print("}")
264
265
266 def printClientVariableNotifyBinding(v):
267     ctype = re.sub ("^gchar", "const gchar", v.ctype);
268     
269     # callback prototype
270     indent = (22 + len (v.c_prefixed_name)) * " "
271     print("typedef void")
272     print("(*%s_changed_callback) (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
273     print("%s%s%s," % (indent, ctype, v.c_name))
274     print("%sgpointer userdata);" % indent)
275     print("")
276     
277     # private callback
278     indent = (20 + len (v.c_prefixed_name)) * " "
279     print("static void")
280     print("_%s_changed_callback (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
281     print("%sconst gchar *variable," % indent)
282     print("%sGValue *value," % indent)
283     print("%sgpointer userdata)" % indent)
284     print("{")
285     print("  GUPnPAsyncData *cbdata;")
286     print("  %s%s;" % (ctype, v.c_name))
287     print("")
288     print("  cbdata = (GUPnPAsyncData *) userdata;")
289     print("  %s = %s (value);" % (v.c_name, v.get_function))
290     print("  ((%s_changed_callback)cbdata->cb)" % v.c_prefixed_name)
291     print("    (proxy,")
292     print("     %s," % v.c_name)
293     print("     cbdata->userdata);")
294     print("")
295     print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
296     print("}")
297     print("")
298     
299     # public notify_add function
300     indent = (13 + len (v.c_prefixed_name)) * " "
301     print("static inline gboolean")
302     print("%s_add_notify (GUPnPServiceProxy *proxy," % v.c_prefixed_name)
303     print("%s%s_changed_callback callback," % (indent, v.c_prefixed_name))
304     print("%sgpointer userdata)" % indent)
305     print("{")
306     print("  GUPnPAsyncData *cbdata;")
307     print("")
308     print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
309     print("  cbdata->cb = G_CALLBACK (callback);")
310     print("  cbdata->userdata = userdata;")
311     print("")
312     print("  return gupnp_service_proxy_add_notify")
313     print("    (proxy,")
314     print("     \"%s\"," % v.name)
315     print("     %s," % v.gtype)
316     print("     _%s_changed_callback," % v.c_prefixed_name)
317     print("     cbdata);")
318     print("}")
319
320
321 def printServerVariableQueryBinding(v):
322     # User callback proto
323     indent = (28 + len (v.ctype)+ len (v.c_prefixed_name)) * " "
324     print("typedef %s(*%s_query_callback) (GUPnPService *service," % (v.ctype, v.c_prefixed_name))
325     print("%sgpointer userdata);" % indent)
326     print("")
327     
328     indent = (12 + len (v.c_prefixed_name)) * " "
329     print("static void")
330     print("_%s_query_cb (GUPnPService *service," % v.c_prefixed_name)
331     print("%sgchar *variable," % indent)
332     print("%sGValue *value," % indent)
333     print("%sgpointer userdata)" % indent)
334     print("{")
335     print("  GUPnPAsyncData *cbdata;")
336     print("  %s%s;" % (v.ctype, v.c_name))
337     print("")
338     
339     indent = (36 + len (v.c_name) + len (v.c_prefixed_name)) * " "
340     print("  cbdata = (GUPnPAsyncData *) userdata;")
341     print("  %s = ((%s_query_callback)cbdata->cb) (service," % (v.c_name, v.c_prefixed_name))
342     print("%scbdata->userdata);" % indent)
343     print("  g_value_init (value, %s);" % v.gtype)
344     print("  %s (value, %s);" % (v.set_function, v.c_name))
345     print("}")
346     print("")
347     
348     indent = (16 + len (v.c_prefixed_name)) * " "
349     print("gulong")
350     print("%s_query_connect (GUPnPService *service," % v.c_prefixed_name)
351     print("%s%s_query_callback callback," % (indent, v.c_prefixed_name))
352     print("%sgpointer userdata)" % indent)
353     print("{")
354     print("  GUPnPAsyncData *cbdata;")
355     print("")
356     print("  cbdata = (GUPnPAsyncData *) g_slice_alloc (sizeof (*cbdata));")
357     print("  cbdata->cb = G_CALLBACK (callback);")
358     print("  cbdata->userdata = userdata;")
359     print("")
360     print("  return g_signal_connect_data (service,")
361     print("                                \"query-variable::%s\"," % v.name)
362     print("                                G_CALLBACK (_%s_query_cb)," % v.c_prefixed_name)
363     print("                                cbdata,")
364     print("                                _free_cb_data,")
365     print("                                (GConnectFlags) 0);")
366     print("}")
367     print("")
368
369
370 def printServerActionBinding(a):
371     # getter and setter func for GUPnPServiceAction
372     indent = (13 + len (a.c_prefixed_name)) * " "
373     if len (a.in_args) > 0:
374         print("static inline void")
375         print("%s_action_get (GUPnPServiceAction *action," % (a.c_prefixed_name))
376         for arg in a.in_args[:-1]:
377             print("%s%s*in_%s," % (indent, arg.related_var.ctype, arg.c_name))
378         print("%s%s*in_%s)" % (indent, a.in_args[-1].related_var.ctype, a.in_args[-1].c_name))
379         print("{")
380         print("  gupnp_service_action_get (action,")
381         for arg in a.in_args:
382             print("                            \"%s\", %s, in_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
383         print("                            NULL);")
384         print("}")
385         print("")
386     if len (a.out_args) > 0:
387         print("static inline void")
388         print("%s_action_set (GUPnPServiceAction *action," % (a.c_prefixed_name))
389         for arg in a.out_args[:-1]:
390             print("%sconst %sout_%s," % (indent, arg.related_var.ctype, arg.c_name))
391         print("%sconst %sout_%s)" % (indent, a.out_args[-1].related_var.ctype, a.out_args[-1].c_name))
392         print("{")
393         print("  gupnp_service_action_set (action,")
394         for arg in a.out_args:
395             print("                            \"%s\", %s, out_%s," % (arg.name, arg.related_var.gtype, arg.c_name))
396         print("                            NULL);")
397         print("}")
398         print("")
399     
400     indent = (17 + len (a.c_prefixed_name)) * " "
401     print("static inline gulong")
402     print("%s_action_connect (GUPnPService *service," % a.c_prefixed_name)
403     print("%sGCallback callback," % indent)
404     print("%sgpointer userdata)" % indent)
405     print("{")
406     print("  return g_signal_connect (service,")
407     print("                           \"action-invoked::%s\"," % a.name)
408     print("                           callback,")
409     print("                           userdata);")
410     print("}")
411     print("")
412
413 def PrintServerVariableNotifyBinding(v):
414     indent = (18 + len (v.c_prefixed_name)) * " "
415     print("void")
416     print("%s_variable_notify (GUPnPService *service," % v.c_prefixed_name)
417     print("%sconst %s%s)" % (indent ,v.ctype, v.c_name))
418     print("{")
419     print("  gupnp_service_notify (service,")
420     print("                        \"%s\", %s, %s," % (v.name, v.gtype, v.c_name))
421     print("                        NULL);")
422     print("}")
423     print("")
424
425 def parseSCPD(scpd, prefix):
426     """
427     Parse the scpd file into lists of Action and Variable objects.
428     """
429     if prefix != "":
430         prefix = prefix.lower() + "_"
431     
432     action_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}actionList/{urn:schemas-upnp-org:service-1-0}action")
433     var_elements = scpd.findall("{urn:schemas-upnp-org:service-1-0}serviceStateTable/{urn:schemas-upnp-org:service-1-0}stateVariable")
434     
435     variables = getVariables(var_elements, prefix)
436     actions = getActions(action_elements, prefix, variables)
437     
438     return (actions, variables)
439
440
441 def printClientBindings(binding_name, actions, variables):
442     define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
443     
444     print("/* Generated by gupnp-binding-tool */")
445     print("")
446     print("#include <libgupnp/gupnp.h>")
447     print("")
448     print("#ifndef %s" % define)
449     print("#define %s" % define)
450     print("")
451     print("G_BEGIN_DECLS")
452
453     print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
454     print("#define __GUPNPASYNCDATA_TYPE__")
455     print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
456     print("#endif")
457
458     for a in actions:
459         print("\n/* action %s */\n" % a.name)
460         printClientSyncActionBinding(a)
461         printClientAsyncActionBinding(a)
462     for v in variables:
463         if (not v.dummy) and v.notified:
464             print("\n/* state variable %s */\n" % v.name)
465             printClientVariableNotifyBinding(v)
466     
467     print("")
468     print("G_END_DECLS")
469     print("")
470     print("#endif /* %s */" % define)
471
472
473 def printServerBindings(binding_name, actions, variables):
474     define = "GUPNP_GENERATED_CLIENT_BINDING_%s" % binding_name
475     
476     print("/* Generated by gupnp-binding-tool */")
477     print("")
478     print("#include <libgupnp/gupnp.h>")
479     print("")
480     print("#ifndef %s" % define)
481     print("#define %s" % define)
482     print("")
483     print("G_BEGIN_DECLS")
484
485     print("\n#ifndef __GUPNPASYNCDATA_TYPE__")
486     print("#define __GUPNPASYNCDATA_TYPE__")
487     print("typedef struct {GCallback cb; gpointer userdata; } GUPnPAsyncData;")
488     print("#endif")
489
490     print("static void")
491     print("_free_cb_data (gpointer data, GClosure *closure)")
492     print("{")
493     print("  GUPnPAsyncData *cbdata = (GUPnPAsyncData *) data;")
494     print("  g_slice_free1 (sizeof (*cbdata), cbdata);")
495     print("}")
496     print("")
497     
498     for a in actions:
499         print("\n/* action %s */\n" % a.name)
500         printServerActionBinding(a)
501     for v in variables:
502         if not v.dummy:
503             print("\n/* state variable %s */\n" % v.name)
504             printServerVariableQueryBinding(v)
505             if v.notified:
506                 PrintServerVariableNotifyBinding(v)
507     
508     print("")
509     print("G_END_DECLS")
510     print("")
511     print("#endif /* %s */" % define)
512
513
514 def main ():
515     parser = OptionParser("Usage: %prog [options] service-filename")
516     parser.add_option("-p", "--prefix", dest="prefix", 
517                       metavar="PREFIX", default="",
518                       help="set prefix for generated function names")
519     parser.add_option("-m", "--mode", type="choice", dest="mode", 
520                       metavar="MODE", default="client",
521                       choices=("client", "server"),
522                       help="select generation mode, 'client' or 'server'")
523     
524     (options, args) = parser.parse_args() 
525     if len(args) != 1:
526         parser.error("Expected 1 argument, got %d" % len(args))
527     
528     # get a name for header ifdef
529     if options.prefix == "":
530         base = re.sub("[^a-zA-Z0-9]", "_", os.path.basename(args[0]))
531         binding_name = base.upper()
532     else:
533         binding_name = options.prefix.upper()
534     
535     # parse scpd file, extract action list and state variable list
536     scpd = ET.parse(args[0])
537     (actions, variables) = parseSCPD (scpd, options.prefix)
538     if (len(actions) == 0 and len(variables) == 0):
539         raise Exception ("No actions or variables found in document")
540     
541     # generate bindings
542     if (options.mode == "client"):
543         printClientBindings(binding_name, actions, variables)
544     else:
545         printServerBindings(binding_name, actions, variables)
546
547
548 if __name__ == "__main__":
549     main()