Add JS wrapper
authorKalle Vahlman <kalle.vahlman@movial.com>
Mon, 3 Nov 2008 13:05:01 +0000 (15:05 +0200)
committerKalle Vahlman <kalle.vahlman@movial.com>
Mon, 3 Nov 2008 13:05:01 +0000 (15:05 +0200)
Add testing tools

html/DBus.js [new file with mode: 0644]
html/unit.html [new file with mode: 0644]
tests/jscorebus-webkit.c [new file with mode: 0644]
tests/unit [new file with mode: 0755]
tests/unit.c [new file with mode: 0644]

diff --git a/html/DBus.js b/html/DBus.js
new file mode 100644 (file)
index 0000000..bf7b654
--- /dev/null
@@ -0,0 +1,177 @@
+/* 
+ * DBus.js, a JS wrapper for the Browser D-Bus Bridge
+ * Copyright © 2008 Movial Creative Technologies Inc
+ *
+ * Contact: Movial Creative Technologies Inc, <info@movial.com>
+ * Authors: Lauri Mylläri, <lauri.myllari@movial.fi>
+ *          Kalle Vahlman, <kalle.vahlman@movial.com>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE. 
+ */
+if (!window.DBus)
+{
+function unWrap(method, user_data) {
+  return function() {
+    if (method)
+      method.apply(user_data, arguments[0]);
+  }
+}
+
+function makeVariantMaker(variant_type) {
+  var ret = function(val) {
+    dump("make " + variant_type + " from " + val + "\n");
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var variant = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant);
+    variant['setAs' + variant_type](val);
+    dump("made " + variant.QueryInterface(Components.interfaces.nsIVariant) + " from " + variant_type + " " + val + "\n");
+    return variant;
+  };
+  return ret;
+}
+
+function DBusWrapper() {
+  dump("DBusWrapper() enter\n");
+
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  this.dbus = Components.classes["@movial.fi/dbus/service;1"].getService();
+  this.dbus = this.dbus.QueryInterface(Components.interfaces.IDBusService);
+  dump("this.dbus: " + this.dbus + "\n");
+
+  this.SYSTEM = this.dbus.SYSTEM;
+  this.SESSION = this.dbus.SESSION;
+
+  var types = {
+    Int32:  "Int32",
+    UInt32: "Uint32",
+    Int16:  "Int16",
+    UInt16: "Uint16",
+    Int64:  "Int64",
+    UInt64: "Uint64",
+    Double: "Double",
+    Byte:   "Int8"
+  };
+
+  /* TODO: These need their own XPCOM types/handling:
+       - ObjectPath
+       - Signature
+       - Variant?
+       - Struct?
+   */
+
+  for (var type in types) {
+    this[type] = makeVariantMaker(types[type]);
+  }
+
+  this.getMethod = function(bustype,
+                            destination,
+                            object_path,
+                            method_name,
+                            interface,
+                            signature,
+                            user_data) {
+    dump("DBusWrapper.getMethod() enter\n");
+    dump("  bustype: " + bustype + "\n");
+    dump("  destination: " + destination + "\n");
+    dump("  object_path: " + object_path + "\n");
+    dump("  method_name: " + method_name + "\n");
+    dump("  interface: " + interface + "\n");
+    dump("  signature: " + signature + "\n");
+    var method = function() {
+      dump("DBusMethod.call()\n");
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      method.dbusmethod.doCall(Array.prototype.slice.call(arguments),
+                               arguments.length);
+    }
+    method.user_data = user_data;
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    method.dbusmethod = this.dbus.getMethod(bustype,
+                                            destination,
+                                            object_path,
+                                            method_name,
+                                            interface,
+                                            signature);
+    method.dbusmethod.onReply = unWrap(method.onreply, method.user_data);
+    method.dbusmethod.onError = unWrap(method.onerror, method.user_data);
+
+    method.watch("onreply", function(id, oldval, newval) {
+                              netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                              method.dbusmethod.onReply = unWrap(newval, method.user_data);
+                            });
+    method.watch("onerror", function(id, oldval, newval) {
+                              netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                              method.dbusmethod.onError = unWrap(newval, method.user_data);
+                            });
+
+    method.async = true;
+    method.watch("async", function(id, oldval, newval) {
+                            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                            method.dbusmethod.async = newval;
+                          });
+    dump("DBusWrapper.getMethod() leave\n");
+    return method;
+  }
+
+  this.getSignal = function(bustype,
+                            interface,
+                            signal_name,
+                            sender,
+                            object_path,
+                            user_data) {
+    dump("DBusWrapper.getSignal() enter\n");
+    dump("  bustype: " + bustype + "\n");
+    dump("  interface: " + interface + "\n");
+    dump("  signal_name: " + signal_name + "\n");
+    dump("  sender: " + sender + "\n");
+    dump("  object_path: " + object_path + "\n");
+
+    var signal = {};
+
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    signal.dbussignal = this.dbus.getSignal(bustype,
+                                            interface,
+                                            signal_name,
+                                            sender,
+                                            object_path);
+    signal.user_data = user_data;
+
+    signal.dbussignal.onEmit = unWrap(signal.onemit, signal.user_data);
+    signal.watch("onemit", function(id, oldval, newval) {
+                             netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                             signal.dbussignal.onEmit = unWrap(newval, signal.user_data);
+                           });
+
+    signal.enabled = false;
+    signal.watch("enabled", function(id, oldval, newval) {
+                              netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                              signal.dbussignal.enabled = newval;
+                            });
+
+    dump("DBusWrapper.getSignal() leave\n");
+    return signal;
+  }
+  dump("DBusWrapper() leave\n");
+}
+
+window.DBus = new DBusWrapper();
+
+}
diff --git a/html/unit.html b/html/unit.html
new file mode 100644 (file)
index 0000000..710b8ce
--- /dev/null
@@ -0,0 +1,377 @@
+<!--
+ Copyright © 2008 Movial Creative Technologies Inc
+ Feel free to use the contents of this file in any way you want
+ If you can make money with it, please contact info@movial.com
+ and you have almost-guaranteed sales position waiting for you!
+ -->
+
+<html>
+<head>
+<title>test</title>
+<script type='text/javascript' src='DBus.js'></script>
+<script type='text/javascript'>
+
+var n_tests = 0;
+var good = 0;
+var bad = 0;
+var current_test = null;
+
+function Test(method_name, arg, convert, xfail, signature, next_test)
+{
+  this.method_name = method_name;
+  this.arg = arg;
+  this.convert = convert;
+  this.signature = signature;
+  this.xfail = xfail;
+  n_tests++;
+  if (!xfail)
+    n_tests++;
+
+  this.next = next_test;
+}
+
+Test.prototype =
+{
+  method_name: null,
+  arg: null,
+  convert: null,
+  xfail: false,
+  signature: null,
+  signal: null,
+  next: null,
+
+  run: function ()
+  {
+    current_test = null;
+    if (!this.method_name)
+    {
+      alert("Test error: No method name specified!");
+      bad++;
+      return;
+    }
+    if (typeof this.arg != 'boolean' && !this.arg)
+    {
+      alert("Test error: No argument specified!");
+      bad++;
+      return;
+    }
+    var method = DBus.getMethod(DBus.SESSION, 'org.movial.Unit',
+                                '/org/movial/Unit',
+                                this.method_name, 'org.movial.Unit', this.signature,
+                                this);
+    this.signal = DBus.getSignal(DBus.SESSION, 'org.movial.Unit', this.method_name,
+                                null, null, this);
+    this.signal.onemit = this.onemit;
+
+    // We need to receive replies as we go to get a meaningful report
+    // but do test with async replies too!
+    method.async = false;
+
+    var status = document.getElementById('status');
+
+    var send_arg;
+    if (this.convert instanceof Function)
+    {
+      send_arg = this.convert(this.arg);
+      if (!send_arg)
+      {
+        var color = 'red';
+        if (this.xfail)
+        {
+          color = 'green';
+          good++;
+        } else {
+          bad++;
+        }
+        status.innerHTML += '<p>Result: <span style="color: ' + color + ';">Converting "' + this.arg + '" failed</span>';
+        current_test = this.next;
+        setTimeout(run_next, 10);
+        return;
+      }
+    } else {
+      send_arg = this.arg;
+    }
+
+    method.onreply = this.method_reply;
+    method.onerror = this.error_reply;
+
+    var argstr;
+    if (this.arg instanceof Array || this.arg instanceof Object)
+    {
+      var first = true;
+      argstr = '['
+      for (var propname in this.arg)
+      {
+        if (first) {
+          first = false;
+        } else {
+          argstr += ", ";
+        }
+        argstr += propname + ":" + this.arg[propname];
+      }
+      argstr += ']';
+    } else {
+      argstr = this.arg.toString();
+    }
+
+    status.innerHTML += '<p>Sending ' + this.method_name + ':<span style="color: blue; padding-left: 1em;">' + argstr + ' of type ' + typeof send_arg + ' with signature ' + this.signature + '</span>';
+    
+    this.signal.enabled = !this.xfail;
+    method(send_arg);
+  },
+
+  onemit: function (reply_arg)
+  {
+    var status = document.getElementById('status');
+    status.innerHTML += '<p>Got signal "' + this.method_name + '"';
+    this.signal.enabled = false;
+    this.method_reply(reply_arg);
+    
+    current_test = this.next;
+    setTimeout(run_next, 10);
+    show_results();
+  },
+  
+  method_reply: function (reply_arg)
+  {
+    var status = document.getElementById('status');
+
+    if (this.xfail)
+    {
+      status.innerHTML += '<p style="margin-top: -0.5em"><span style="color:red; padding-left: 1em;">This test should have failed, but did not!</span>';
+      bad++;
+      current_test = this.next;
+      setTimeout(run_next, 10);
+      show_results();
+      return;
+    }
+    
+    var color = 'green';
+    if (!is_equal(this.arg, reply_arg))
+      color = 'red';
+
+    var argstr;
+
+    if (reply_arg instanceof Array || reply_arg instanceof Object)
+    {
+      var first = true;
+      argstr = '['
+      for (var propname in reply_arg)
+      {
+        if (first) {
+          first = false;
+        } else {
+          argstr += ", ";
+        }
+        argstr += propname + ":" + reply_arg[propname];
+      }
+      argstr += ']';
+    } else {
+      if (typeof this.arg == 'boolean')
+      {
+        argstr = reply_arg.toString();
+      } else if (reply_arg) {
+        argstr = reply_arg.toString();
+      } else {
+        argstr = '';
+      }
+    }
+
+    if (color == 'red')
+    {
+      bad++;
+    } else {
+      good++;
+    }
+    
+    status.innerHTML += '<p style="margin-top: -0.5em">Result: <span style="color: ' + color + '; padding-left: 1em;">' + argstr + ', ' + typeof reply_arg + '</span>';
+
+    show_results();
+  },
+
+  error_reply: function (error_name, error_message)
+  {
+    var status = document.getElementById('status');
+
+    if (this.xfail)
+    {
+      status.innerHTML += '<p style="color: green;">Expected failure:';
+      good++;
+    } else {
+      status.innerHTML += '<p style="color: red;">' + error_name + ':';
+      bad++;
+    }
+    status.innerHTML += '<span style="color: red; padding-left: 1em;">' + error_message + '</span>';
+
+    current_test = this.next;
+    setTimeout(run_next, 10);
+    show_results();
+  },
+
+};
+
+function is_equal(a, b)
+{
+  if ((a instanceof Array && b instanceof Array)
+    || (a instanceof Object && b instanceof Object))
+  {
+    for (var i in a)
+    {
+      if (!is_equal(a[i], b[i]))
+      {
+        return false;
+      }
+    }
+
+  } else if (a != b) {
+    return false;
+  }
+  
+  return true;
+}
+
+function run_next()
+{
+  if (current_test) {
+    current_test.run();
+    return;
+  }
+
+  var status = document.getElementById('status');
+  status.innerHTML += "<p>Ending test...";
+  var end = DBus.getMethod(DBus.SESSION, 'org.movial.Unit',
+                                '/org/movial/Unit',
+                                "end", 'org.movial.Unit');
+  end();
+  show_results();
+}
+
+function show_results()
+{
+  var results = document.getElementById('results');
+  results.innerHTML = '<p>Total of ' + n_tests + ' tests, <span style="color: green">passed ' + good + '</span>, <span style="color:red">failed ' + bad + '</span>, unknown ' + (n_tests - good - bad) + ", " + Math.round((good/n_tests)*100) + '% success rate';
+}
+
+var tests = new Array();
+
+function do_unit()
+{
+  var status = document.getElementById('status');
+  
+  setTimeout(show_results, 1000);
+  
+  status.innerHTML += "<p>Starting test...";
+  var start = DBus.getMethod(DBus.SESSION, 'org.movial.Unit',
+                                '/org/movial/Unit',
+                                "start", 'org.movial.Unit');
+  start();
+
+  var i = 0;
+
+  tests[i++] = new Test('Boolean', true, null, false);
+  tests[i++] = new Test('Boolean', true, null, false, 'b');
+
+  tests[i++] = new Test('Int16', 32767, DBus.Int16, false);
+  tests[i++] = new Test('Int16', 32767, null, true);
+  tests[i++] = new Test('Int16', 32767, DBus.Int16, false, 'n');
+  tests[i++] = new Test('Int16', 32767, null, false, 'n');
+
+  tests[i++] = new Test('Int32', 2147483647, DBus.Int32, false);
+  tests[i++] = new Test('Int32', 2147483647, null, true);
+  tests[i++] = new Test('Int32', 2147483647, DBus.Int32, false, 'i');
+  tests[i++] = new Test('Int32', 2147483647, null, false, 'i');
+
+  // There's problems with the (u)int64 types since doubles can't hold
+  // large enough values accurately.
+  tests[i++] = new Test('Int64', 17179869176, DBus.Int64, false);
+  tests[i++] = new Test('Int64', 17179869176, null, true);
+  tests[i++] = new Test('Int64', 17179869176, DBus.Int64, false, 'x');
+  tests[i++] = new Test('Int64', 17179869176, null, false, 'x');
+
+  tests[i++] = new Test('UInt16', 32767, DBus.UInt16, false);
+  tests[i++] = new Test('UInt16', 32767, null, true);
+  tests[i++] = new Test('UInt16', 32767, DBus.UInt16, false, 'q');
+  tests[i++] = new Test('UInt16', 32767, null, false, 'q');
+
+  tests[i++] = new Test('UInt32', 2147483647, DBus.UInt32, false);
+  tests[i++] = new Test('UInt32', 2147483647, null, true);
+  tests[i++] = new Test('UInt32', 2147483647, DBus.UInt32, false, 'u');
+  tests[i++] = new Test('UInt32', 2147483647, null, false, 'u');
+
+  tests[i++] = new Test('UInt64', 17179869176, DBus.UInt64, false);
+  tests[i++] = new Test('UInt64', 17179869176, null, true);
+  tests[i++] = new Test('UInt64', 17179869176, DBus.UInt64, false, 't');
+  tests[i++] = new Test('UInt64', 17179869176, null, false, 't');
+
+  tests[i++] = new Test('Double', 1024.1024, null, false);
+  tests[i++] = new Test('Double', 1024.1024, null, false, 'd');
+
+  tests[i++] = new Test('Byte', 1, DBus.Byte, false);
+  tests[i++] = new Test('Byte', 1, null, true);
+  tests[i++] = new Test('Byte', 1, DBus.Byte, false, 'y');
+  tests[i++] = new Test('Byte', 1, null, false, 'y');
+
+  tests[i++] = new Test('String', "Test string", null, false);
+  tests[i++] = new Test('String', "Test string", null, false, 's');
+
+  tests[i++] = new Test('ObjectPath', "/org/movial/Unit", DBus.ObjectPath, false);
+  tests[i++] = new Test('ObjectPath', "/org/movial/Unit", null, true);
+  tests[i++] = new Test('ObjectPath', "/org/movial/Unit", DBus.ObjectPath, false, 'o');
+  tests[i++] = new Test('ObjectPath', "I'm invalid object path", DBus.ObjectPath, true);
+  tests[i++] = new Test('ObjectPath', "/not//valid/either", DBus.ObjectPath, true);
+
+  tests[i++] = new Test('Signature', "sib", DBus.Signature, false);
+  tests[i++] = new Test('Signature', "sib", null, true);
+  tests[i++] = new Test('Signature', "sib", DBus.Signature, false, 'g');
+  tests[i++] = new Test('Signature', "sib", null, false, 'g');
+
+  tests[i++] = new Test("Array", ["woot", "bleep", "wap"], null, false, null);
+  tests[i++] = new Test("Array", [1, 2, 3], null, false, null);
+  tests[i++] = new Test("Array", ["woot", "bleep", "wap"], null, false, 'as');
+  tests[i++] = new Test("Array", [1, 2, 3], null, false, 'ai');
+
+  var dict = new Object();
+  dict["test"] = "me";
+  dict["test2"] = "you";
+
+  tests[i++] = new Test("Dict", dict, null, false, 'a{ss}');
+  tests[i++] = new Test("Dict", dict, null, false);
+
+  var dict = new Object();
+  dict["test"] = [1, 2, 3];
+  dict["test2"] = [4, 5, 6];
+  tests[i++] = new Test("Dict", dict, null, false, 'a{sad}');
+
+/* TODO: We don't really yet know how exactly we want these to go...
+  tests[i++] = new Test("Variant", "woot", DBus.Variant, false);
+  tests[i++] = new Test("Variant", 1, DBus.Variant, false);
+  tests[i++] = new Test("Variant", "woot", DBus.Variant, false, 'v');
+  tests[i++] = new Test("Variant", ["woot", "bar"], DBus.Variant, false, 'v');
+
+  var struct = new Object();
+  struct.what = "that";
+  struct.how_many = 42;
+
+  tests[i++] = new Test("Struct", struct, DBus.Struct, false);
+  tests[i++] = new Test("Struct", struct, DBus.Struct, false, '(si)');
+*/
+
+  for (var j = 0; j < i-1; j++)
+  {
+    tests[j].next = tests[j+1]
+  }
+  current_test = tests[0];
+  run_next();
+
+}
+
+</script>
+</head>
+<body onload='do_unit();'>
+<div><a href="unit.html">Run again</a> <a href="http://google.fi">Leave the page</a></div>
+<div id='results' style='border: solid blue 0.1em; padding: 1em; margin: 1em;'></div>
+<div id='status' style='border: dashed black 0.1em; padding: 1em;'></div>
+
+</body>
+</html>
+
diff --git a/tests/jscorebus-webkit.c b/tests/jscorebus-webkit.c
new file mode 100644 (file)
index 0000000..13720f7
--- /dev/null
@@ -0,0 +1,106 @@
+/**
+ * Browser D-Bus Bridge, JavaScriptCore version
+ *
+ * Copyright © 2008 Movial Creative Technologies Inc
+ *  Contact: Movial Creative Technologies Inc, <info@movial.com>
+ *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/*
+ * To build, execute this (on one line):
+ *
+ *   gcc -o jscorebus-webkit jscorebus-webkit.c \
+ *       $(pkg-config --cflags --libs webkit-1.0 dbus-glib-1 jscorebus)
+ *
+ */
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <webkit/webkit.h>
+#include <JavaScriptCore/JavaScript.h>
+#include <jscorebus/jscorebus.h>
+
+GtkWidget *view;
+
+static
+void _window_object_cleared (WebKitWebView* web_view,
+                             WebKitWebFrame* frame,
+                             JSGlobalContextRef context,
+                             JSObjectRef windowObject,
+                             gpointer user_data)
+{
+  jscorebus_export(context);
+}
+
+static gboolean
+_window_delete_event(GtkWidget *widget, GdkEvent  *event, gpointer user_data)
+{
+  gtk_widget_destroy(widget);
+  gtk_main_quit();
+  return TRUE;
+}
+
+int main(int argc, char *argv[])
+{
+  GtkWidget *window;
+  GtkWidget *swin;
+  DBusConnection *session_connection;
+  DBusConnection *system_connection;
+
+  gtk_init(&argc, &argv);
+
+  if (argc < 2)
+  {
+    g_print("Usage: %s <url>\n", argv[0]);
+    return(1);
+  }
+  
+  session_connection = dbus_bus_get(DBUS_BUS_SESSION, NULL);
+  system_connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+
+  dbus_connection_setup_with_g_main(session_connection, NULL);
+  dbus_connection_setup_with_g_main(system_connection, NULL);
+  
+  jscorebus_init(session_connection, system_connection);
+  
+  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  swin = gtk_scrolled_window_new(NULL, NULL);
+  view = webkit_web_view_new();
+  
+  gtk_container_add(GTK_CONTAINER(swin), view);
+  gtk_container_add(GTK_CONTAINER(window), swin);
+  
+  g_signal_connect(window, "delete-event",
+                   G_CALLBACK(_window_delete_event), NULL);
+
+  /***
+   * Connect to the window-object-cleared signal. This is the moment when we
+   * need to install the D-Bus bindings into the DOM.
+   */
+  g_signal_connect(view, "window-object-cleared",
+                   G_CALLBACK(_window_object_cleared), session_connection);
+
+  webkit_web_view_open(WEBKIT_WEB_VIEW(view), argv[1]);
+  gtk_widget_set_size_request(window,
+                              640,
+                              480);
+  gtk_widget_show_all(window);
+  gtk_main();
+
+  return (0);
+}
+
diff --git a/tests/unit b/tests/unit
new file mode 100755 (executable)
index 0000000..9a27843
Binary files /dev/null and b/tests/unit differ
diff --git a/tests/unit.c b/tests/unit.c
new file mode 100644 (file)
index 0000000..d2d70a3
--- /dev/null
@@ -0,0 +1,494 @@
+/**
+ * Unit, a D-Bus tester for argument marshalling
+ *
+ * Copyright © 2008 Movial Creative Technologies Inc
+ *  Contact: Movial Creative Technologies Inc, <info@movial.com>
+ *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * The Unit interface has methods that take a certain argument types and
+ * send a reply and a subsequent signal with the received arguments.
+ *
+ * To build, execute this:
+ *
+ *   gcc -o unit unit.c $(pkg-config --cflags --libs dbus-glib-1)
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#define OBJECT_PATH "/org/movial/Unit"
+#define SERVICE_NAME "org.movial.Unit"
+
+GMainLoop *loop;
+
+static gboolean u_transfer_array(DBusMessageIter *to_iter, DBusMessageIter *from_iter);
+static gboolean u_transfer_variant(DBusMessageIter *to_iter, DBusMessageIter *from_iter);
+static gboolean u_transfer_dict(DBusMessageIter *to_iter, DBusMessageIter *from_iter);
+static gboolean u_transfer_struct(DBusMessageIter *to_iter, DBusMessageIter *from_iter);
+
+static void
+u_unregister(DBusConnection *connection, void *user_data)
+{
+  g_debug("Object path unregistered");
+  g_main_loop_quit(loop);
+}
+
+static void
+u_transfer_arg(DBusMessageIter *to, DBusMessageIter *from)
+{
+  dbus_uint64_t value;
+  int type = dbus_message_iter_get_arg_type(from);
+  
+  if (!dbus_type_is_basic(type))
+  {
+    switch (type)
+    {
+      case DBUS_TYPE_ARRAY:
+        u_transfer_array(to, from);
+        break;
+      case DBUS_TYPE_VARIANT:
+        u_transfer_variant(to, from);
+        break;
+      case DBUS_TYPE_DICT_ENTRY:
+        u_transfer_dict(to, from);
+        break;
+      case DBUS_TYPE_STRUCT:
+        u_transfer_struct(to, from);
+        break;
+      default:
+        g_warning("Non-basic type '%c' in variants not yet handled", type);
+        break;
+    }
+    return;
+  }
+
+  dbus_message_iter_get_basic(from, &value);
+  if (type == DBUS_TYPE_STRING)
+  {
+    g_debug("Transferring string arg %s", value);
+  }
+  if (type == DBUS_TYPE_INT32)
+  {
+    g_debug("Transferring int arg %i", value);
+  }
+  dbus_message_iter_append_basic(to, type, &value);
+}
+
+static gboolean
+u_transfer_variant(DBusMessageIter *to_iter, DBusMessageIter *from_iter)
+{
+  DBusMessageIter to;
+  DBusMessageIter from;
+  const char *sig = NULL;
+  
+  dbus_message_iter_recurse(from_iter, &from);
+  sig = dbus_message_iter_get_signature(&from);
+
+  if (sig == NULL)
+    return FALSE;
+
+  dbus_message_iter_open_container(to_iter, DBUS_TYPE_VARIANT, sig, &to);
+
+  do {
+    u_transfer_arg(&to, &from);
+  } while (dbus_message_iter_next(&from));
+  
+  dbus_message_iter_close_container(to_iter, &to);
+  
+  return TRUE;
+}
+
+static gboolean
+u_transfer_array(DBusMessageIter *to_iter, DBusMessageIter *from_iter)
+{
+  DBusMessageIter to;
+  DBusMessageIter from;
+  int elem_type;
+  char *sig = NULL;
+  
+  dbus_message_iter_recurse(from_iter, &from);
+
+  elem_type = dbus_message_iter_get_element_type(from_iter);
+  switch (elem_type)
+  {
+    case DBUS_TYPE_ARRAY:
+      {
+        /* Fetch the element type for this too */
+        sig = g_strdup(dbus_message_iter_get_signature(from_iter)+1);
+        break;
+      }
+    case DBUS_TYPE_DICT_ENTRY:
+    case DBUS_TYPE_STRUCT:
+      g_warning("Element type '%c' in arrays not supported", elem_type);
+      break;
+    default:
+      sig = g_strdup_printf("%c", dbus_message_iter_get_element_type(from_iter));
+      break;
+  }
+
+  if (sig == NULL)
+    return FALSE;
+
+  dbus_message_iter_open_container(to_iter, DBUS_TYPE_ARRAY, sig, &to);
+
+  do {
+    u_transfer_arg(&to, &from);
+  } while (dbus_message_iter_next(&from));
+  
+  dbus_message_iter_close_container(to_iter, &to);
+
+  return TRUE;
+}
+
+static gboolean
+u_transfer_dict(DBusMessageIter *to_iter, DBusMessageIter *from_iter)
+{
+  DBusMessageIter to;
+  DBusMessageIter from;
+  char *sig = NULL;
+
+  dbus_message_iter_recurse(from_iter, &from);
+  sig = g_strdup(dbus_message_iter_get_signature(&from));
+
+  if (sig == NULL)
+    return FALSE;
+
+  dbus_message_iter_open_container(to_iter, DBUS_TYPE_ARRAY, sig, &to);
+  do {
+    DBusMessageIter subfrom;
+    DBusMessageIter dictiter;
+    dbus_message_iter_open_container(&to, DBUS_TYPE_DICT_ENTRY,
+                                                    NULL, &dictiter);
+    dbus_message_iter_recurse(&from, &subfrom);
+
+    u_transfer_arg(&dictiter, &subfrom);
+    if (dbus_message_iter_next(&subfrom)) {
+      u_transfer_arg(&dictiter, &subfrom);
+    }
+    dbus_message_iter_close_container(&to, &dictiter);
+  } while (dbus_message_iter_next(&from));
+  
+  dbus_message_iter_close_container(to_iter, &to);
+  
+  return TRUE;
+}
+
+static gboolean
+u_transfer_struct(DBusMessageIter *to_iter, DBusMessageIter *from_iter)
+{
+  DBusMessageIter to;
+  DBusMessageIter from;
+
+  dbus_message_iter_recurse(from_iter, &from);
+  dbus_message_iter_open_container(to_iter, DBUS_TYPE_STRUCT, NULL, &to);
+
+  do {
+    u_transfer_arg(&to, &from);
+  } while (dbus_message_iter_next(&from));
+  
+  dbus_message_iter_close_container(to_iter, &to);
+  
+  return TRUE;
+}
+
+static DBusHandlerResult
+u_message(DBusConnection *connection, DBusMessage *message, void *user_data)
+{
+#ifdef DEBUG  
+  const char *path = dbus_message_get_path(message);
+  const char *interface = dbus_message_get_interface(message);
+  const char *member = dbus_message_get_member(message);
+  const char *signature = dbus_message_get_signature(message);
+
+  g_debug("Message received, path %s, interface %s, member %s, signature %s",
+          path == NULL ? "not set" : path,
+          interface == NULL ? "not set" : interface,
+          member == NULL ? "not set" : member,
+          signature == NULL ? "not set" : signature);
+#endif
+  
+  if (!dbus_message_has_path(message, OBJECT_PATH))
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "start"))
+  {
+    DBusMessage *reply;
+
+    /* TODO: start logging */
+    
+    reply = dbus_message_new_method_return(message);
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "end"))
+  {
+    DBusMessage *reply;
+
+    /* TODO: stop logging */
+    
+    reply = dbus_message_new_method_return(message);
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+  if (strlen(dbus_message_get_signature(message)) == 0)
+  {
+    DBusMessage *reply = dbus_message_new_error(message,
+                                                SERVICE_NAME ".ArgError",
+                                                "Empty signature");
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+#define BASIC_TYPE(method_name, dtype) \
+  if (dbus_message_is_method_call(message, SERVICE_NAME, method_name)) \
+  { \
+    dbus_uint64_t value = 0; \
+    DBusError err; \
+    DBusMessage *reply = NULL; \
+    DBusMessage *signal = NULL; \
+    dbus_error_init(&err); \
+    if (!dbus_message_get_args(message, &err, \
+                               DBUS_TYPE_ ## dtype, &value, \
+                               DBUS_TYPE_INVALID)) \
+    { \
+      reply = dbus_message_new_error(message, \
+                                     SERVICE_NAME ".ArgError", \
+                                     err.message); \
+      g_debug(err.message); \
+      g_assert(reply != NULL); \
+    } else { \
+      reply = dbus_message_new_method_return(message); \
+      g_assert(reply != NULL); \
+      dbus_message_append_args(reply, \
+                               DBUS_TYPE_ ## dtype, &value, \
+                               DBUS_TYPE_INVALID); \
+      signal = dbus_message_new_signal(OBJECT_PATH, SERVICE_NAME, method_name); \
+      g_debug("Appending %llu to signal", value); \
+      dbus_message_append_args(signal, \
+                               DBUS_TYPE_ ## dtype, &value, \
+                               DBUS_TYPE_INVALID); \
+    } \
+    dbus_connection_send(connection, reply, NULL); \
+    dbus_message_unref(reply); \
+    if (signal != NULL) \
+    { \
+      dbus_connection_send(connection, signal, NULL); \
+      dbus_message_unref(signal); \
+    } \
+    return DBUS_HANDLER_RESULT_HANDLED; \
+  }
+  
+  BASIC_TYPE("Boolean", BOOLEAN);
+  BASIC_TYPE("Byte", BYTE);
+  BASIC_TYPE("Int16", INT16);
+  BASIC_TYPE("Int32", INT32);
+  BASIC_TYPE("Int64", INT64);
+  BASIC_TYPE("UInt16", UINT16);
+  BASIC_TYPE("UInt32", UINT32);
+  BASIC_TYPE("UInt64", UINT64);
+  BASIC_TYPE("Double", DOUBLE);
+  BASIC_TYPE("String", STRING);
+  BASIC_TYPE("ObjectPath", OBJECT_PATH);
+  BASIC_TYPE("Signature", SIGNATURE);
+  
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "Array"))
+  {
+    DBusMessageIter iter;
+    DBusMessageIter reply_iter;
+    DBusMessage *reply;
+    DBusMessage *signal;
+
+    dbus_message_iter_init(message, &iter);
+
+    reply = dbus_message_new_method_return(message);
+
+    dbus_message_iter_init_append(reply, &reply_iter);
+    u_transfer_array(&reply_iter, &iter);
+
+    dbus_message_iter_init(message, &iter);
+    signal = dbus_message_new_signal(OBJECT_PATH, SERVICE_NAME, "Array");
+    dbus_message_iter_init_append(signal, &reply_iter);
+    u_transfer_array(&reply_iter, &iter);
+
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    dbus_connection_send(connection, signal, NULL);
+    dbus_message_unref(signal);
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "Variant"))
+  {
+    DBusMessageIter iter;
+    DBusMessageIter reply_iter;
+    DBusMessage *reply;
+    DBusMessage *signal = NULL;
+
+    if (!g_str_equal(dbus_message_get_signature(message), "v"))
+    {
+      reply = dbus_message_new_error(message,
+                                     SERVICE_NAME ".ArgError",
+                                     "Signature mismatch");
+      dbus_connection_send(connection, reply, NULL);
+      dbus_message_unref(reply);
+      return DBUS_HANDLER_RESULT_HANDLED;
+    }
+    dbus_message_iter_init(message, &iter);
+
+    reply = dbus_message_new_method_return(message);
+    dbus_message_iter_init_append(reply, &reply_iter);
+    u_transfer_variant(&reply_iter, &iter);
+
+    dbus_message_iter_init(message, &iter);
+    signal = dbus_message_new_signal(OBJECT_PATH, SERVICE_NAME, "Variant");
+    dbus_message_iter_init_append(signal, &reply_iter);
+    u_transfer_variant(&reply_iter, &iter);
+
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    dbus_connection_send(connection, signal, NULL);
+    dbus_message_unref(signal);
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "Dict"))
+  {
+    DBusMessageIter iter;
+    DBusMessageIter reply_iter;
+    DBusMessage *reply;
+    DBusMessage *signal;
+
+    dbus_message_iter_init(message, &iter);
+
+    reply = dbus_message_new_method_return(message);
+    dbus_message_iter_init_append(reply, &reply_iter);
+
+    u_transfer_dict(&reply_iter, &iter);
+
+    dbus_message_iter_init(message, &iter);
+    signal = dbus_message_new_signal(OBJECT_PATH, SERVICE_NAME, "Dict");
+    dbus_message_iter_init_append(signal, &reply_iter);
+    u_transfer_dict(&reply_iter, &iter);
+
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    dbus_connection_send(connection, signal, NULL);
+    dbus_message_unref(signal);
+    
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+  if (dbus_message_is_method_call(message, SERVICE_NAME, "Struct"))
+  {
+    DBusMessageIter iter;
+    DBusMessageIter reply_iter;
+    DBusMessage *reply;
+    DBusMessage *signal;
+    const char *signature = dbus_message_get_signature(message);
+    
+    if (signature[0] != '(')
+       return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    dbus_message_iter_init(message, &iter);
+
+    reply = dbus_message_new_method_return(message);
+    dbus_message_iter_init_append(reply, &reply_iter);
+
+    u_transfer_struct(&reply_iter, &iter);
+
+    dbus_message_iter_init(message, &iter);
+    signal = dbus_message_new_signal(OBJECT_PATH, SERVICE_NAME, "Struct");
+    dbus_message_iter_init_append(signal, &reply_iter);
+    u_transfer_struct(&reply_iter, &iter);
+
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+    dbus_connection_send(connection, signal, NULL);
+    dbus_message_unref(signal);
+    
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+
+#ifdef DEBUG
+  g_debug("Didn't handle the message");
+#endif
+
+  
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int main(int argc, char **argv)
+{
+  DBusError err;
+  DBusConnection* conn;
+  DBusObjectPathVTable vtable;
+  int ret;
+
+  dbus_error_init(&err);
+  conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
+  if (dbus_error_is_set(&err)) { 
+    g_warning("Connection Error (%s)\n", err.message); 
+    dbus_error_free(&err); 
+  }
+  g_assert(conn != NULL);
+
+  vtable.unregister_function = u_unregister;
+  vtable.message_function = u_message;
+
+  if (!dbus_connection_register_object_path(conn, OBJECT_PATH,
+                                            &vtable, NULL))
+  {
+    g_error("Could not register object path");
+  }
+
+  ret = dbus_bus_request_name(conn, SERVICE_NAME, 
+                              DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
+  if (dbus_error_is_set(&err)) { 
+    g_warning("Requesting service name failed (%s)\n", err.message); 
+    dbus_error_free(&err); 
+  }
+  g_assert(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER == ret);
+
+  dbus_bus_add_match(conn, "type='method_call'", &err);
+  if (dbus_error_is_set(&err)) { 
+    g_warning("Add match failed (%s)\n", err.message); 
+    dbus_error_free(&err);
+    exit(1);
+  }
+
+  loop = g_main_loop_new(NULL, FALSE);
+  dbus_connection_setup_with_g_main(conn, NULL);
+  
+       g_print("Unit ready to accept method calls\n");
+  g_main_loop_run(loop);
+  
+  return 0;
+}