Skip to content
This repository
Browse code

Adding windmill2 code to repository.

  • Loading branch information...
commit 8acec68be70dac26766e220c91efa573a858bc65 1 parent a8effa3
Mikeal Rogers authored August 18, 2009

Showing 45 changed files with 5,923 additions and 1 deletion. Show diff stats Hide diff stats

  1. 2  setup.py
  2. 0  windmill2/__init__.py
  3. 0  windmill2/browser/__init__.py
  4. 3  windmill2/browser/extension/chrome.manifest
  5. 36  windmill2/browser/extension/content/overlay.js
  6. 5  windmill2/browser/extension/content/overlay.xul
  7. 19  windmill2/browser/extension/install.rdf
  8. 13  windmill2/browser/firefox.py
  9. 0  windmill2/castile/__init__.py
  10. 454  windmill2/castile/events.py
  11. 469  windmill2/castile/js/json2.js
  12. 147  windmill2/castile/js/node.js
  13. 144  windmill2/castile/ror.py
  14. 120  windmill2/chat_push.html
  15. 71  windmill2/chat_push.py
  16. 59  windmill2/cli.py
  17. 75  windmill2/docs/Makefile
  18. BIN  windmill2/docs/_build/doctrees/environment.pickle
  19. BIN  windmill2/docs/_build/doctrees/index.doctree
  20. 241  windmill2/docs/_build/html/_sources/index.txt
  21. BIN  windmill2/docs/_build/html/_static/contents.png
  22. 657  windmill2/docs/_build/html/_static/default.css
  23. 232  windmill2/docs/_build/html/_static/doctools.js
  24. BIN  windmill2/docs/_build/html/_static/file.png
  25. 32  windmill2/docs/_build/html/_static/jquery.js
  26. BIN  windmill2/docs/_build/html/_static/minus.png
  27. BIN  windmill2/docs/_build/html/_static/navigation.png
  28. BIN  windmill2/docs/_build/html/_static/plus.png
  29. 61  windmill2/docs/_build/html/_static/pygments.css
  30. 16  windmill2/docs/_build/html/_static/rightsidebar.css
  31. 467  windmill2/docs/_build/html/_static/searchtools.js
  32. 557  windmill2/docs/_build/html/_static/sphinxdoc.css
  33. 19  windmill2/docs/_build/html/_static/stickysidebar.css
  34. 700  windmill2/docs/_build/html/_static/traditional.css
  35. 143  windmill2/docs/_build/html/genindex.html
  36. 308  windmill2/docs/_build/html/index.html
  37. 102  windmill2/docs/_build/html/modindex.html
  38. 17  windmill2/docs/_build/html/objects.inv
  39. 89  windmill2/docs/_build/html/search.html
  40. 1  windmill2/docs/_build/html/searchindex.js
  41. 190  windmill2/docs/conf.py
  42. 241  windmill2/docs/index.rst
  43. 95  windmill2/push_test.html
  44. 97  windmill2/tests/test_castile.py
  45. 42  windmill2/wsgi_push.py
2  setup.py
@@ -52,7 +52,7 @@
52 52
       license='http://www.apache.org/licenses/LICENSE-2.0',
53 53
       include_package_data = True,
54 54
       packages = find_packages(exclude=['test', 'trac-files', 'tutorial', 'test.test_live', 'scripts', 
55  
-                                        'flash', 'contrib']),
  55
+                                        'flash', 'contrib', 'windmill2']),
56 56
       package_data = {'': ['*.js', '*.css', '*.html', '*.txt', '*.xpi',
57 57
                            '*.crt', '*.key', '*.csr', 'cert8.db' ],},
58 58
       platforms =['Any'],
0  windmill2/__init__.py
No changes.
0  windmill2/browser/__init__.py
No changes.
3  windmill2/browser/extension/chrome.manifest
... ...
@@ -0,0 +1,3 @@
  1
+content windmill content/
  2
+
  3
+overlay chrome://browser/content/browser.xul chrome://windmill/content/overlay.xul
36  windmill2/browser/extension/content/overlay.js
... ...
@@ -0,0 +1,36 @@
  1
+// ***** BEGIN LICENSE BLOCK *****
  2
+// Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3
+// 
  4
+// The contents of this file are subject to the Mozilla Public License Version
  5
+// 1.1 (the "License"); you may not use this file except in compliance with
  6
+// the License. You may obtain a copy of the License at
  7
+// http://www.mozilla.org/MPL/
  8
+// 
  9
+// Software distributed under the License is distributed on an "AS IS" basis,
  10
+// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11
+// for the specific language governing rights and limitations under the
  12
+// License.
  13
+// 
  14
+// The Original Code is Mozilla Corporation Code.
  15
+// 
  16
+// The Initial Developer of the Original Code is
  17
+// Mikeal Rogers.
  18
+// Portions created by the Initial Developer are Copyright (C) 2009
  19
+// the Initial Developer. All Rights Reserved.
  20
+// 
  21
+// Contributor(s):
  22
+//  Mikeal Rogers <mikeal.rogers@gmail.com>
  23
+// 
  24
+// Alternatively, the contents of this file may be used under the terms of
  25
+// either the GNU General Public License Version 2 or later (the "GPL"), or
  26
+// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27
+// in which case the provisions of the GPL or the LGPL are applicable instead
  28
+// of those above. If you wish to allow use of your version of this file only
  29
+// under the terms of either the GPL or the LGPL, and not to allow others to
  30
+// use your version of this file under the terms of the MPL, indicate your
  31
+// decision by deleting the provisions above and replace them with the notice
  32
+// and other provisions required by the GPL or the LGPL. If you do not delete
  33
+// the provisions above, a recipient may use your version of this file under
  34
+// the terms of any one of the MPL, the GPL or the LGPL.
  35
+
  36
+// This is where any special extension only initialization happens
5  windmill2/browser/extension/content/overlay.xul
... ...
@@ -0,0 +1,5 @@
  1
+<?xml version="1.0"?>
  2
+<overlay id="pushmarks-overlay"
  3
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
  4
+  <script src="overlay.js"/>
  5
+</overlay>
19  windmill2/browser/extension/install.rdf
... ...
@@ -0,0 +1,19 @@
  1
+<?xml version="1.0"?>
  2
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  3
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  4
+   <Description about="urn:mozilla:install-manifest">
  5
+     <em:id>windmill@mozilla.com</em:id>
  6
+     <em:name>Windmill</em:name>
  7
+     <em:version>2.0</em:version>
  8
+     <em:creator>Mikeal Rogers</em:creator>
  9
+     <em:description>Windmill ROCKS!.</em:description>
  10
+     <em:targetApplication>
  11
+       <!-- Firefox -->
  12
+       <Description>
  13
+         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
  14
+         <em:minVersion>1.5</em:minVersion>
  15
+         <em:maxVersion>3.2a1pre</em:maxVersion>
  16
+       </Description>
  17
+     </em:targetApplication>
  18
+   </Description>
  19
+</RDF>
13  windmill2/browser/firefox.py
... ...
@@ -0,0 +1,13 @@
  1
+import os, sys
  2
+import tempfile
  3
+from distutils import dir_util
  4
+copytree = dir_util.copy_tree
  5
+
  6
+this_dir = os.path.abspath(os.path.dirname(__file__))
  7
+windmill_dir = os.path.abspath(os.path.dirname(this_dir))
  8
+
  9
+def create_extension():
  10
+    t = tempfile.mkdtemp(prefix='windmill2.')
  11
+    copytree(os.path.join(this_dir, 'extension'), t)
  12
+    copytree(os.path.join(windmill_dir, 'castile', 'js'), os.path.join(t, 'resource', 'castile'))
  13
+    return t
0  windmill2/castile/__init__.py
No changes.
454  windmill2/castile/events.py
... ...
@@ -0,0 +1,454 @@
  1
+import simplejson
  2
+from time import sleep
  3
+import uuid
  4
+from urlparse import urlparse
  5
+
  6
+import httplib2
  7
+
  8
+from webenv import Response, Request, Response303, Response404
  9
+from webenv.rest import RestApplication
  10
+import threading
  11
+
  12
+import ror
  13
+
  14
+def with_slash(uri):
  15
+    if uri.endswith('/'): return uri
  16
+    else: return uri+'/'
  17
+
  18
+
  19
+class JSONStream(Response):
  20
+    """Response object that blocks forever streaming event objects."""
  21
+    running = False
  22
+    content_type = 'application/json'
  23
+    def __init__(self, client):
  24
+        self.client = client
  25
+        self.running = True
  26
+    def __iter__(self):
  27
+        while self.running:
  28
+            sleep(.5)
  29
+            if len(self.client.events) is not 0:
  30
+                yield simplejson.dumps(self.client.events.pop(0))
  31
+            else:
  32
+                yield ''
  33
+
  34
+class EventClient(object):
  35
+    def __init__(self, ns, obj):
  36
+        self.ns = ns
  37
+        self.registration_obj = obj
  38
+        self.event_node_type = obj['event-node-type']
  39
+        if self.event_node_type == 'http-rest-api':
  40
+            self.http_client = httplib2.Http()
  41
+            self.http_events_uri = with_slash(obj['events-uri'])
  42
+        self.events = []
  43
+        self.streaming_responses = []
  44
+        
  45
+    def listener(self, obj):
  46
+        """Local listener for remote events node."""
  47
+        event_type = obj['event-type']
  48
+        if self.event_node_type == 'http-rest-api':
  49
+            self.http_push(event_type, obj)
  50
+        else:
  51
+            self.events.append((event_type, obj,))
  52
+            
  53
+    def get_remote_object(self, name):
  54
+        return ror.create_remote(self, name)
  55
+            
  56
+    def describe(self, object_id, depth=0):
  57
+        if self.event_node_type == 'http-rest-api':
  58
+            path = self.http_events_uri+self.ns+'/ror/describe/'+object_id+'?depth='+str(depth)
  59
+            response, content = self.http_client.request(path, 'GET', 
  60
+                                                         headers={'content-type':'application/json'})
  61
+            assert response.status == 200
  62
+            return simplejson.loads(content)
  63
+        elif self.event_node_type == 'streaming-push':
  64
+            pass # TODO: Implement a push on to the response stack then block until a response is pushed
  65
+    
  66
+    def callFunction(self, object_id, args, kwargs):
  67
+        if self.event_node_type == 'http-rest-api':
  68
+            path = self.http_events_uri+self.ns+'/ror/callFunction/'+object_id
  69
+            if args is None and kwargs is None:
  70
+                body = None
  71
+            else:
  72
+                body = {'args':args, 'kwargs':kwargs}
  73
+            response, content = self.http_client.request(path, 'POST', body=simplejson.dumps(body),
  74
+                                                         headers={'content-type':'application/json'})
  75
+            assert response.status == 200
  76
+            return simplejson.loads(content)
  77
+        elif self.event_node_type == 'streaming-push':
  78
+            pass # TODO: Implement a push on to the response stack then block until a response is pushed
  79
+    
  80
+    def createInstance(self, object_id, args, kwargs):
  81
+        if self.event_node_type == 'http-rest-api':
  82
+            path = self.http_events_uri+self.ns+'/ror/createInstance/'+object_id
  83
+            if args is None and kwargs is None:
  84
+                body = None
  85
+            else:
  86
+                body = {'args':args, 'kwargs':kwargs}
  87
+            response, content = self.http_client.request(path, 'POST', body=simplejson.dumps(body),
  88
+                                                         headers={'content-type':'application/json'})
  89
+            assert response.status == 200
  90
+            return simplejson.loads(content)
  91
+        elif self.event_node_type == 'streaming-push':
  92
+            pass # TODO: Implement a push on to the response stack then block until a response is pushed
  93
+            
  94
+    def setAttribute(self, object_id, attr, obj, reference=False):
  95
+        if self.event_node_type == 'http-rest-api':
  96
+            path = self.http_events_uri+self.ns+'/ror/setAttribute/'+object_id
  97
+            body = {'attribute':attr, 'value':obj, 'reference':False}
  98
+            response, content = self.http_client.request(path, 'POST', body=simplejson.dumps(body), 
  99
+                                                         headers={'content-type':'application/json'})
  100
+            assert response.status == 200
  101
+            return simplejson.loads(content)
  102
+        elif self.event_node_type == 'streaming-push':
  103
+            pass # TODO: Implement a push on to the response stack then block until a response is pushed
  104
+    
  105
+    def setItem(self, object_id, attr, obj, reference=False):
  106
+        if self.event_node_type == 'http-rest-api':
  107
+            path = self.http_events_uri+self.ns+'/ror/setItem/'+object_id
  108
+            body = {'attribute':attr, 'value':obj, 'reference':False}
  109
+            response, content = self.http_client.request(path, 'POST', body=simplejson.dumps(body), 
  110
+                                                         headers={'content-type':'application/json'})
  111
+            assert response.status == 200
  112
+            return simplejson.loads(content)
  113
+        elif self.event_node_type == 'streaming-push':
  114
+            pass # TODO: Implement a push on to the response stack then block until a response is pushed
  115
+    
  116
+    def http_push(self, event_type, obj):
  117
+        """Method which pushes an event to the remote events node."""
  118
+        body = simplejson.dumps(obj)
  119
+        response, content = self.http_client.request(self.http_events_uri+event_type, 'PUT', 
  120
+                                 body=body, headers={'content-type':'application/json'})
  121
+        assert response.status == 200
  122
+    
  123
+    def add_remote_listener(self, event_type, obj):
  124
+        obj['namespace'] = event_type
  125
+        client_ns = obj['client-namespace']
  126
+        body = simplejson.dumps(obj)
  127
+        response, content = self.http_client.request(self.http_events_uri+client_ns+'/listeners', 
  128
+                                 'POST', body=body, headers={'content-type':'application/json'})
  129
+        assert response.status == 200
  130
+        
  131
+    def get_json_stream(self):
  132
+        for s in self.streaming_responses:
  133
+            s.running = False
  134
+        s = JSONStream(self)
  135
+        self.streaming_responses.append(s)
  136
+        return s
  137
+
  138
+def getRORType(obj):
  139
+    if obj is None:
  140
+        return 'null', None
  141
+    t = type(obj)
  142
+    if t in [int, float, unicode, str, list, tuple, set, dict]:
  143
+        if t in (int, float,):
  144
+            return str(t.__name__), obj
  145
+        elif t in (unicode, str,):
  146
+            return 'string', obj
  147
+        elif t in (list, tuple, set,):
  148
+            return 'array', list(obj)
  149
+        elif t == dict:
  150
+            return 'hash', obj
  151
+    elif callable(obj):
  152
+        return 'function', None
  153
+    elif isinstance(obj, obj.__class__):
  154
+        return 'instanceobject', None
  155
+    else:
  156
+        return 'classobject', None            
  157
+
  158
+
  159
+def eToDict(e):
  160
+    d = dict(
  161
+        [ (x, getattr(e, x),) for x in dir(e) 
  162
+            if type(getattr(e, x,)) in [int, float, str, unicode, dict, list, tuple]
  163
+        ])
  164
+    d.pop('__dict__')
  165
+    d['execptionType'] = type(e).__name__
  166
+    return d
  167
+        
  168
+
  169
+class ROR(object):
  170
+    def __init__(self):
  171
+        self.object_map = {}
  172
+        self.registry = {}
  173
+    def add_object(self, name, obj):
  174
+        self.object_map[name] = obj
  175
+    def describe(self, name, depth=0):
  176
+        if name is None:
  177
+            return {'objects':[self.describe(x) for x in self.object_map.keys()]}
  178
+        
  179
+        if depth == -1:
  180
+            depth = 99
  181
+        # try:
  182
+        obj = self.get_object(name)
  183
+        # except AttributeError:
  184
+        #     return {'exception':'No such attribute'}
  185
+        t, value = getRORType(obj)
  186
+        desc = {'name':name, 'type':t, 'value':value}
  187
+        if depth == 0:
  188
+            return desc
  189
+        else:
  190
+            desc['attributes'] = [self.describe(name+'.'+x, depth=(depth - 1)) for x in dir(obj) if not x.startswith('_')]
  191
+        return desc 
  192
+    
  193
+    def get_object(self, name):
  194
+        
  195
+        def getitem(obj, n):
  196
+            index = n.split('[')[1:][0][:-1]
  197
+            try:
  198
+                index = int(index)
  199
+            except:
  200
+                index = index.replace('"', '').replace("'", '')
  201
+            return obj[index]
  202
+        
  203
+        name = name.split('.')
  204
+        if name[0].startswith('registry'):
  205
+            reg = name.pop(0)
  206
+            if '"' in reg: s = '"'
  207
+            else: s = "'"
  208
+            k = reg.split(s)[1]
  209
+            obj = self.registry[k]
  210
+        else:
  211
+            n = name.pop(0)
  212
+            if '[' in n:
  213
+                obj = getitem(self.object_map[n.split('[')[0]], n)
  214
+            else:
  215
+                obj = self.object_map[n]
  216
+        
  217
+        for n in name:
  218
+            if '[' in n:
  219
+                obj = getitem(getattr(obj, n.split('[')[0]), n)
  220
+            else:
  221
+                obj = getattr(obj, n)
  222
+        return obj
  223
+        
  224
+    def callFunction(self, name, args, kwargs):
  225
+        obj = self.get_object(name)
  226
+        u = str(uuid.uuid1())
  227
+        try:
  228
+            self.registry[u] = obj(*args, **kwargs)
  229
+        except Exception, e:
  230
+            return eToDict(e)
  231
+        t, value = getRORType(self.registry[u])
  232
+        desc = {'name':'registry["'+u+'"]', 'type':t, 'value':value}
  233
+        return desc
  234
+        
  235
+    def createInstance(self, name, args, kwargs):
  236
+        return self.callFunction(name, args, kwargs)
  237
+        
  238
+    def setAttribute(self, name, attr, obj, reference=False):
  239
+        if reference is True:
  240
+            obj = self.get_object(obj)
  241
+
  242
+        setattr(self.get_object(name), attr, obj)
  243
+    
  244
+    def setItem(self, name, attr, obj, reference=False):
  245
+        if reference is True:
  246
+            obj = self.get_object(obj)
  247
+        
  248
+        self.get_object(name)[attr] = obj
  249
+        
  250
+    
  251
+class CastileNode(object):
  252
+    def __init__(self, ns, events_uri):
  253
+        self.ns = ns
  254
+        self.events_uri = events_uri
  255
+        self.client_map = {}
  256
+        self.ror = ROR()
  257
+        self.listeners = {}
  258
+        # Register self
  259
+        obj = {'event-node-type':'http-rest-api', 'events-uri':events_uri}
  260
+        self.register(ns, obj)
  261
+        
  262
+    def describe(self, client_id, object_id, depth=0):
  263
+        if client_id == self.ns:
  264
+            return self.ror.describe(object_id, depth)
  265
+        else:
  266
+            return self.client_map[client_id].describe(object_id, depth)
  267
+            
  268
+    def get_remote_object(self, client_id, name):
  269
+        return self.client_map[client_id].get_remote_object(name)
  270
+            
  271
+    def callFunction(self, client_id, ror_name, args, kwargs):
  272
+        if client_id == self.ns:
  273
+            return self.ror.callFunction(ror_name, args, kwargs)
  274
+        else:
  275
+            return self.client_map[client_id].callFunction(ror_name, args, kwargs)
  276
+    
  277
+    def createInstance(self, client_id, ror_name, args, kwargs):
  278
+        if client_id == self.ns:
  279
+            return self.ror.createInstance(ror_name, args, kwargs)
  280
+        else:
  281
+            return self.client_map[client_id].createInstance(ror_name, args, kwargs)
  282
+            
  283
+    def setAttribute(self, client_id, object_id, attr, value, reference=False):
  284
+        if client_id == self.ns:
  285
+            return self.ror.setAttribute(object_id, attr, value, reference)
  286
+        else:
  287
+            return self.client_map[client_id].setAttribute(object_id, attr, value, reference)
  288
+    
  289
+    def setItem(self, client_id, object_id, attr, value, reference=False):
  290
+        if client_id == self.ns:
  291
+            return self.ror.setItem(object_id, attr, value, reference)
  292
+        else:
  293
+            return self.client_map[client_id].setItem(object_id, attr, value, reference)
  294
+        
  295
+    def register(self, ns, obj):
  296
+        obj['client-namespace'] = ns
  297
+        self.client_map[ns] = EventClient(ns, obj)
  298
+        self.fire_event('castile.register', obj)
  299
+        return ns
  300
+        
  301
+    def two_way_register_listener(self, obj):
  302
+        if obj['events-uri'] != self.events_uri and obj['client-namespace'] != self.ns:
  303
+            self.register_with_node(obj['events-uri'])
  304
+                
  305
+    def register_with_node(self, uri):
  306
+        h = httplib2.Http()
  307
+        response, content = h.request(with_slash(uri), method='POST',
  308
+                                      body=simplejson.dumps(self.client_map[self.ns].registration_obj)) 
  309
+        assert response.status == 200
  310
+        
  311
+    def add_listener(self, ns, func):
  312
+        if not ns.startswith(self.ns+'.') and ns != self.ns:
  313
+            def get_client(split):
  314
+                if self.client_map.has_key('.'.join(split)):
  315
+                    return self.client_map['.'.join(split)]
  316
+                elif len(split) is not 0:
  317
+                    return get_client(split[:-1])
  318
+            
  319
+            client = get_client(ns.split('.'))
  320
+            if client is not None:
  321
+                client.add_remote_listener(ns, self.client_map[self.ns].registration_obj)
  322
+        self.listeners.setdefault(ns, []).append(func)
  323
+    
  324
+    def _fire_event(self, ns, obj):
  325
+        obj['event-type'] = ns
  326
+        heirarchy = []
  327
+        x = ''
  328
+        for n in ns.split('.'):
  329
+            x += n 
  330
+            heirarchy.append(x)
  331
+            x += '.'
  332
+        
  333
+        for h in heirarchy:
  334
+            if self.listeners.has_key(h):
  335
+                for listener in self.listeners[h]:
  336
+                    listener(obj)
  337
+                    
  338
+    fire_event = lambda self, ns, obj: self._fire_event(self.ns+'.'+ns, obj)
  339
+    
  340
+    def get_application(self):
  341
+        return CastileApplication(self)
  342
+        
  343
+    def get_cherrypy_server(self, host='0.0.0.0', port=None, 
  344
+                            server_name=None, numthreads=50):
  345
+        from cherrypy import wsgiserver
  346
+        
  347
+        if port is None:
  348
+            port = urlparse(self.events_uri).port
  349
+            if port is None:
  350
+                port = 80
  351
+        
  352
+        if server_name is None:
  353
+            server_name = self.ns+'-server'
  354
+        
  355
+        self.httpd = wsgiserver.CherryPyWSGIServer((host, port,), self.get_application(), 
  356
+                                                   server_name=server_name, 
  357
+                                                   numthreads=numthreads)
  358
+        return self.httpd
  359
+    
  360
+    def start_cherrypy_server(self, host='0.0.0.0', port=None, 
  361
+                              server_name=None, numthreads=50, threaded=True):
  362
+        self.get_cherrypy_server(host, port, server_name, numthreads)
  363
+        if threaded is True:
  364
+            self.thread = threading.Thread(target=self.httpd.start)
  365
+            self.thread.start()
  366
+            return self.httpd
  367
+        else:
  368
+            self.httpd.start()
  369
+            return self.httpd
  370
+
  371
+class CastileApplication(RestApplication):
  372
+    def __init__(self, events_node):
  373
+        super(CastileApplication, self).__init__()
  374
+        self.events_node = events_node
  375
+        self.events_uri = self.events_node.events_uri
  376
+    
  377
+    def PUT(self, request, ns):
  378
+        self.events_node._fire_event(ns, simplejson.loads(str(request.body)))
  379
+        return Response('')
  380
+    
  381
+    def POST(self, request, client_ns=None, action=None, ror_action=None, ror_name=None):
  382
+        """Client API POST handler"""
  383
+        body = str(request.body)
  384
+        if len(body) is 0:
  385
+            obj = None
  386
+        else:
  387
+            obj = simplejson.loads(body)
  388
+        if client_ns is None:
  389
+            # POST to / is a remote node registration call
  390
+            namespace = obj.pop('client-namespace')
  391
+            client_ns = self.events_node.register(namespace, obj)
  392
+            return Response303(with_slash(request.reconstructed_url)+client_ns)
  393
+            
  394
+        elif action == 'listeners':
  395
+            # POST to /$clientId/listeners adds a remote listener
  396
+            namespace = obj.pop('namespace')
  397
+            listener = self.events_node.client_map[client_ns].listener
  398
+            self.events_node.add_listener(namespace, listener)
  399
+            return Response303(with_slash(request.reconstructed_url)+namespace)
  400
+        elif action == 'ror':
  401
+            if ror_action == 'callFunction' or ror_action == 'createInstance':                
  402
+                if obj:
  403
+                    if obj.has_key('args'): args = obj['args']
  404
+                    else: args = [] 
  405
+                    if obj.has_key('kwargs'): kwargs = obj['kwargs']
  406
+                    else: kwargs = {}
  407
+                else:
  408
+                    args = []
  409
+                    kwargs = {}
  410
+                resp = getattr(self.events_node, ror_action)(client_ns, ror_name, args, kwargs)
  411
+                return Response(simplejson.dumps(resp))
  412
+            if ror_action == 'setItem' or ror_action == 'setAttribute':
  413
+                args = {'attr':obj['attribute'], 'value':obj['value']}
  414
+                if obj.has_key('reference'):
  415
+                    args['reference'] = obj['reference']
  416
+                resp = getattr(self.events_node, ror_action)(client_ns, ror_name, **args)
  417
+                return Response(simplejson.dumps(resp))
  418
+        
  419
+    def GET(self, request, client_ns=None, action=None, rid=None, object_name=None):
  420
+        if client_ns is None:
  421
+            # GET to / returns a list of clients
  422
+            return Response('Not Implemented GET '+request.reconstructed_url) # TODO: Return list of clients
  423
+        elif action == None:
  424
+            # Get to /$clientId returns the info object for that remote node
  425
+            return Response(simplejson.dumps(self.events_node.client_map[client_ns].registration_obj)) 
  426
+        elif action == 'jsonstream':
  427
+            # Get to /$clientId/jsonstream returns a stream of JSON objects.
  428
+            return self.events_node.client_map[client_ns].get_json_stream()
  429
+        elif action == 'listeners':
  430
+            if rid is None:
  431
+                # Get to /$clientId/listeners retuns a list of listeners
  432
+                return Response('Not Implemented GET '+request.reconstructed_url) #TODO: Return listeners info
  433
+            else:
  434
+                # Get to /$clientId/listeners/$namespace returns the info for a listener
  435
+                return Response('Not Implemented GET '+request.reconstructed_url) #TODO: Return listener info
  436
+        elif action == 'ror':
  437
+            return Response(simplejson.dumps(getattr(self.events_node, rid)(client_ns, object_name)))
  438
+    
  439
+    def DELETE(self, request, client_ns=None, action=None, rid=None):
  440
+        pass
  441
+
  442
+# 
  443
+# class CastileNodeApplication(RestApplication):
  444
+#     """WSGI Application for accessing the event node."""
  445
+#     def __init__(self, events_node):
  446
+#         super(CastileNodeApplication, self).__init__()
  447
+#         self.events_node = events_node
  448
+#         self.add_resource('clients', ClientApplication(events_node))
  449
+    
  450
+# # TODO: Move verbage from events to node and push client/ under / instead of under events
  451
+#     
  452
+# def get_application():
  453
+#     return CastileNodeApplication(event_type)
  454
+#         
469  windmill2/castile/js/json2.js
... ...
@@ -0,0 +1,469 @@
  1
+/*
  2
+    http://www.JSON.org/json2.js
  3
+    2008-05-25
  4
+
  5
+    Public Domain.
  6
+
  7
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  8
+
  9
+    See http://www.JSON.org/js.html
  10
+
  11
+    This file creates a global JSON object containing two methods: stringify
  12
+    and parse.
  13
+
  14
+        JSON.stringify(value, replacer, space)
  15
+            value       any JavaScript value, usually an object or array.
  16
+
  17
+            replacer    an optional parameter that determines how object
  18
+                        values are stringified for objects without a toJSON
  19
+                        method. It can be a function or an array.
  20
+
  21
+            space       an optional parameter that specifies the indentation
  22
+                        of nested structures. If it is omitted, the text will
  23
+                        be packed without extra whitespace. If it is a number,
  24
+                        it will specify the number of spaces to indent at each
  25
+                        level. If it is a string (such as '\t' or '&nbsp;'),
  26
+                        it contains the characters used to indent at each level.
  27
+
  28
+            This method produces a JSON text from a JavaScript value.
  29
+
  30
+            When an object value is found, if the object contains a toJSON
  31
+            method, its toJSON method will be called and the result will be
  32
+            stringified. A toJSON method does not serialize: it returns the
  33
+            value represented by the name/value pair that should be serialized,
  34
+            or undefined if nothing should be serialized. The toJSON method
  35
+            will be passed the key associated with the value, and this will be
  36
+            bound to the object holding the key.
  37
+
  38
+            For example, this would serialize Dates as ISO strings.
  39
+
  40
+                Date.prototype.toJSON = function (key) {
  41
+                    function f(n) {
  42
+                        // Format integers to have at least two digits.
  43
+                        return n < 10 ? '0' + n : n;
  44
+                    }
  45
+
  46
+                    return this.getUTCFullYear()   + '-' +
  47
+                         f(this.getUTCMonth() + 1) + '-' +
  48
+                         f(this.getUTCDate())      + 'T' +
  49
+                         f(this.getUTCHours())     + ':' +
  50
+                         f(this.getUTCMinutes())   + ':' +
  51
+                         f(this.getUTCSeconds())   + 'Z';
  52
+                };
  53
+
  54
+            You can provide an optional replacer method. It will be passed the
  55
+            key and value of each member, with this bound to the containing
  56
+            object. The value that is returned from your method will be
  57
+            serialized. If your method returns undefined, then the member will
  58
+            be excluded from the serialization.
  59
+
  60
+            If the replacer parameter is an array, then it will be used to
  61
+            select the members to be serialized. It filters the results such
  62
+            that only members with keys listed in the replacer array are
  63
+            stringified.
  64
+
  65
+            Values that do not have JSON representations, such as undefined or
  66
+            functions, will not be serialized. Such values in objects will be
  67
+            dropped; in arrays they will be replaced with null. You can use
  68
+            a replacer function to replace those with JSON values.
  69
+            JSON.stringify(undefined) returns undefined.
  70
+
  71
+            The optional space parameter produces a stringification of the
  72
+            value that is filled with line breaks and indentation to make it
  73
+            easier to read.
  74
+
  75
+            If the space parameter is a non-empty string, then that string will
  76
+            be used for indentation. If the space parameter is a number, then
  77
+            the indentation will be that many spaces.
  78
+
  79
+            Example:
  80
+
  81
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
  82
+            // text is '["e",{"pluribus":"unum"}]'
  83
+
  84
+
  85
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  86
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  87
+
  88
+            text = JSON.stringify([new Date()], function (key, value) {
  89
+                return this[key] instanceof Date ?
  90
+                    'Date(' + this[key] + ')' : value;
  91
+            });
  92
+            // text is '["Date(---current time---)"]'
  93
+
  94
+
  95
+        JSON.parse(text, reviver)
  96
+            This method parses a JSON text to produce an object or array.
  97
+            It can throw a SyntaxError exception.
  98
+
  99
+            The optional reviver parameter is a function that can filter and
  100
+            transform the results. It receives each of the keys and values,
  101
+            and its return value is used instead of the original value.
  102
+            If it returns what it received, then the structure is not modified.
  103
+            If it returns undefined then the member is deleted.
  104
+
  105
+            Example:
  106
+
  107
+            // Parse the text. Values that look like ISO date strings will
  108
+            // be converted to Date objects.
  109
+
  110
+            myData = JSON.parse(text, function (key, value) {
  111
+                var a;
  112
+                if (typeof value === 'string') {
  113
+                    a =
  114
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  115
+                    if (a) {
  116
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  117
+                            +a[5], +a[6]));
  118
+                    }
  119
+                }
  120
+                return value;
  121
+            });
  122
+
  123
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  124
+                var d;
  125
+                if (typeof value === 'string' &&
  126
+                        value.slice(0, 5) === 'Date(' &&
  127
+                        value.slice(-1) === ')') {
  128
+                    d = new Date(value.slice(5, -1));
  129
+                    if (d) {
  130
+                        return d;
  131
+                    }
  132
+                }
  133
+                return value;
  134
+            });
  135
+
  136
+
  137
+    This is a reference implementation. You are free to copy, modify, or
  138
+    redistribute.
  139
+
  140
+    This code should be minified before deployment.
  141
+    See http://javascript.crockford.com/jsmin.html
  142
+
  143
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  144
+    NOT CONTROL.
  145
+*/
  146
+
  147
+/*jslint evil: true */
  148
+
  149
+/*global JSON */
  150
+
  151
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
  152
+    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
  153
+    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
  154
+    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
  155
+    test, toJSON, toString
  156
+*/
  157
+
  158
+var EXPORTED_SYMBOLS = ["JSON"];
  159
+
  160
+// Create a JSON object only if one does not already exist. We create the
  161
+// object in a closure to avoid creating global variables.
  162
+
  163
+    JSON = function () {
  164
+
  165
+        function f(n) {
  166
+            // Format integers to have at least two digits.
  167
+            return n < 10 ? '0' + n : n;
  168
+        }
  169
+
  170
+        Date.prototype.toJSON = function (key) {
  171
+
  172
+            return this.getUTCFullYear()   + '-' +
  173
+                 f(this.getUTCMonth() + 1) + '-' +
  174
+                 f(this.getUTCDate())      + 'T' +
  175
+                 f(this.getUTCHours())     + ':' +
  176
+                 f(this.getUTCMinutes())   + ':' +
  177
+                 f(this.getUTCSeconds())   + 'Z';
  178
+        };
  179
+
  180
+        var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  181
+            escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  182
+            gap,
  183
+            indent,
  184
+            meta = {    // table of character substitutions
  185
+                '\b': '\\b',
  186
+                '\t': '\\t',
  187
+                '\n': '\\n',
  188
+                '\f': '\\f',
  189
+                '\r': '\\r',
  190
+                '"' : '\\"',
  191
+                '\\': '\\\\'
  192
+            },
  193
+            rep;
  194
+
  195
+
  196
+        function quote(string) {
  197
+
  198
+// If the string contains no control characters, no quote characters, and no
  199
+// backslash characters, then we can safely slap some quotes around it.
  200
+// Otherwise we must also replace the offending characters with safe escape
  201
+// sequences.
  202
+
  203
+            escapeable.lastIndex = 0;
  204
+            return escapeable.test(string) ?
  205
+                '"' + string.replace(escapeable, function (a) {
  206
+                    var c = meta[a];
  207
+                    if (typeof c === 'string') {
  208
+                        return c;
  209
+                    }
  210
+                    return '\\u' + ('0000' +
  211
+                            (+(a.charCodeAt(0))).toString(16)).slice(-4);
  212
+                }) + '"' :
  213
+                '"' + string + '"';
  214
+        }
  215
+
  216
+
  217
+        function str(key, holder) {
  218
+
  219
+// Produce a string from holder[key].
  220
+
  221
+            var i,          // The loop counter.
  222
+                k,          // The member key.
  223
+                v,          // The member value.
  224
+                length,
  225
+                mind = gap,
  226
+                partial,
  227
+                value = holder[key];
  228
+
  229
+// If the value has a toJSON method, call it to obtain a replacement value.
  230
+
  231
+            if (value && typeof value === 'object' &&
  232
+                    typeof value.toJSON === 'function') {
  233
+                value = value.toJSON(key);
  234
+            }
  235
+
  236
+// If we were called with a replacer function, then call the replacer to
  237
+// obtain a replacement value.
  238
+
  239
+            if (typeof rep === 'function') {
  240
+                value = rep.call(holder, key, value);
  241
+            }
  242
+
  243
+// What happens next depends on the value's type.
  244
+
  245
+            switch (typeof value) {
  246
+            case 'string':
  247
+                return quote(value);
  248
+
  249
+            case 'number':
  250
+
  251
+// JSON numbers must be finite. Encode non-finite numbers as null.
  252
+
  253
+                return isFinite(value) ? String(value) : 'null';
  254
+
  255
+            case 'boolean':
  256
+            case 'null':
  257
+
  258
+// If the value is a boolean or null, convert it to a string. Note:
  259
+// typeof null does not produce 'null'. The case is included here in
  260
+// the remote chance that this gets fixed someday.
  261
+
  262
+                return String(value);
  263
+
  264
+// If the type is 'object', we might be dealing with an object or an array or
  265
+// null.
  266
+
  267
+            case 'object':
  268
+
  269
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
  270
+// so watch out for that case.
  271
+
  272
+                if (!value) {
  273
+                    return 'null';
  274
+                }
  275
+
  276
+// Make an array to hold the partial results of stringifying this object value.
  277
+
  278
+                gap += indent;
  279
+                partial = [];
  280
+
  281
+// If the object has a dontEnum length property, we'll treat it as an array.
  282
+
  283
+                if (typeof value.length === 'number' &&
  284
+                        !(value.propertyIsEnumerable('length'))) {
  285
+
  286
+// The object is an array. Stringify every element. Use null as a placeholder
  287
+// for non-JSON values.
  288
+
  289
+                    length = value.length;
  290
+                    for (i = 0; i < length; i += 1) {
  291
+                        partial[i] = str(i, value) || 'null';
  292
+                    }
  293
+
  294
+// Join all of the elements together, separated with commas, and wrap them in
  295
+// brackets.
  296
+
  297
+                    v = partial.length === 0 ? '[]' :
  298
+                        gap ? '[\n' + gap +
  299
+                                partial.join(',\n' + gap) + '\n' +
  300
+                                    mind + ']' :
  301
+                              '[' + partial.join(',') + ']';
  302
+                    gap = mind;
  303
+                    return v;
  304
+                }
  305
+
  306
+// If the replacer is an array, use it to select the members to be stringified.
  307
+
  308
+                if (rep && typeof rep === 'object') {
  309
+                    length = rep.length;
  310
+                    for (i = 0; i < length; i += 1) {
  311
+                        k = rep[i];
  312
+                        if (typeof k === 'string') {
  313
+                            v = str(k, value, rep);
  314
+                            if (v) {
  315
+                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
  316
+                            }
  317
+                        }
  318
+                    }
  319
+                } else {
  320
+
  321
+// Otherwise, iterate through all of the keys in the object.
  322
+
  323
+                    for (k in value) {
  324
+                        if (Object.hasOwnProperty.call(value, k)) {
  325
+                            v = str(k, value, rep);
  326
+                            if (v) {
  327
+                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
  328
+                            }
  329
+                        }
  330
+                    }
  331
+                }
  332
+
  333
+// Join all of the member texts together, separated with commas,
  334
+// and wrap them in braces.
  335
+
  336
+                v = partial.length === 0 ? '{}' :
  337
+                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
  338
+                            mind + '}' : '{' + partial.join(',') + '}';
  339
+                gap = mind;
  340
+                return v;
  341
+            }
  342
+        }
  343
+
  344
+// Return the JSON object containing the stringify and parse methods.
  345
+
  346
+        return {
  347
+            stringify: function (value, replacer, space) {
  348
+
  349
+// The stringify method takes a value and an optional replacer, and an optional
  350
+// space parameter, and returns a JSON text. The replacer can be a function
  351
+// that can replace values, or an array of strings that will select the keys.
  352
+// A default replacer method can be provided. Use of the space parameter can
  353
+// produce text that is more easily readable.
  354
+
  355
+                var i;
  356
+                gap = '';
  357
+                indent = '';
  358
+
  359
+// If the space parameter is a number, make an indent string containing that
  360
+// many spaces.
  361
+
  362
+                if (typeof space === 'number') {
  363
+                    for (i = 0; i < space; i += 1) {
  364
+                        indent += ' ';
  365
+                    }
  366
+
  367
+// If the space parameter is a string, it will be used as the indent string.
  368
+
  369
+                } else if (typeof space === 'string') {
  370
+                    indent = space;
  371
+                }
  372
+
  373
+// If there is a replacer, it must be a function or an array.
  374
+// Otherwise, throw an error.
  375
+
  376
+                rep = replacer;
  377
+                if (replacer && typeof replacer !== 'function' &&
  378
+                        (typeof replacer !== 'object' ||
  379
+                         typeof replacer.length !== 'number')) {
  380
+                    throw new Error('JSON.stringify');
  381
+                }
  382
+
  383
+// Make a fake root object containing our value under the key of ''.
  384
+// Return the result of stringifying the value.
  385
+
  386
+                return str('', {'': value});
  387
+            },
  388
+
  389
+
  390
+            parse: function (text, reviver) {
  391
+
  392
+// The parse method takes a text and an optional reviver function, and returns
  393
+// a JavaScript value if the text is a valid JSON text.
  394
+
  395
+                var j;
  396
+
  397
+                function walk(holder, key) {
  398
+
  399
+// The walk method is used to recursively walk the resulting structure so
  400
+// that modifications can be made.
  401
+
  402
+                    var k, v, value = holder[key];
  403
+                    if (value && typeof value === 'object') {
  404
+                        for (k in value) {
  405
+                            if (Object.hasOwnProperty.call(value, k)) {
  406
+                                v = walk(value, k);
  407
+                                if (v !== undefined) {
  408
+                                    value[k] = v;
  409
+                                } else {
  410
+                                    delete value[k];
  411
+                                }
  412
+                            }
  413
+                        }
  414
+                    }
  415
+                    return reviver.call(holder, key, value);
  416
+                }
  417
+
  418
+
  419
+// Parsing happens in four stages. In the first stage, we replace certain
  420
+// Unicode characters with escape sequences. JavaScript handles many characters
  421
+// incorrectly, either silently deleting them, or treating them as line endings.
  422
+
  423
+                cx.lastIndex = 0;
  424
+                if (cx.test(text)) {
  425
+                    text = text.replace(cx, function (a) {
  426
+                        return '\\u' + ('0000' +
  427
+                                (+(a.charCodeAt(0))).toString(16)).slice(-4);
  428
+                    });
  429
+                }
  430
+
  431
+// In the second stage, we run the text against regular expressions that look
  432
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
  433
+// because they can cause invocation, and '=' because it can cause mutation.
  434
+// But just to be safe, we want to reject all unexpected forms.
  435
+
  436
+// We split the second stage into 4 regexp operations in order to work around
  437
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
  438
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  439
+// replace all simple value tokens with ']' characters. Third, we delete all
  440
+// open brackets that follow a colon or comma or that begin the text. Finally,
  441
+// we look to see that the remaining characters are only whitespace or ']' or
  442
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  443
+
  444
+                if (/^[\],:{}\s]*$/.
  445
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
  446
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
  447
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  448
+
  449
+// In the third stage we use the eval function to compile the text into a
  450
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  451
+// in JavaScript: it can begin a block or an object literal. We wrap the text
  452
+// in parens to eliminate the ambiguity.
  453
+
  454
+                    j = eval('(' + text + ')');
  455
+
  456
+// In the optional fourth stage, we recursively walk the new structure, passing
  457
+// each name/value pair to a reviver function for possible transformation.
  458
+
  459
+                    return typeof reviver === 'function' ?
  460
+                        walk({'': j}, '') : j;
  461
+                }
  462
+
  463
+// If the text is not JSON parseable, then a SyntaxError is thrown.
  464
+
  465
+                throw new SyntaxError('JSON.parse');
  466
+            }
  467
+        };
  468
+    }();
  469
+
147  windmill2/castile/js/node.js
... ...
@@ -0,0 +1,147 @@
  1
+var EXPORTED_SYMBOLS = ["RemoteNode", "EventNode"];
  2
+
  3
+Components.utils.import("resource://windmill/castile/json2.js")
  4
+
  5
+var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
  6
+    .getService(Components.interfaces.nsIUUIDGenerator);
  7
+
  8
+
  9
+var doRequest = function (method, url, body) {
  10
+  if (body == undefined) {
  11
+    var body = null;
  12
+  }
  13
+  var XMLHttpRequest = utils.getMethodInWindows('XMLHttpRequest');
  14
+  var req = new XMLHttpRequest();  
  15
+  req.open(method, url, false, username, password);
  16
+  req.setRequestHeader('User-Agent', 'windmill2');
  17
+  req.setRequestHeader('Content-Type', "application/json")
  18
+  req.send(body);
  19
+  if (req.status != 200) {
  20
+    throw "Request to delicious fails, status code "+req.status+". Message: "+String(req.responseText);
  21
+  }
  22
+  return req.responseText;
  23
+}
  24
+
  25
+
  26
+var RemoteNode = function (obj) {
  27
+  this.uri = obj.uri;
  28
+  this.ns = obj.namespace;
  29
+  this.description = obj;
  30
+}
  31
+RemoteNode.prototype.describe = function (name, depth) {
  32
+  if (depth == undefined) {
  33
+    var depth = 0;
  34
+  }
  35
+  var rURI = this.uri + '/' + this.ns + '/ror/describe/' + name + "?depth=" + String(depth);
  36
+  return JSON.parse(doRequest("GET", rURI));
  37
+}
  38
+RemoteNode.prototype.callFunction = function (name, args, kwargs) {
  39
+  var rURI = this.uri + '/' + this.ns + '/ror/callFunction/' + name;
  40
+  var body  = {"args":args, "kwargs":kwargs};
  41
+  return JSON.parse(doRequest("POST", rURI, JSON.stringify(body)));
  42
+}
  43
+RemoteNode.prototype.createInstance = function (name, args, kwargs) {
  44
+  var rURI = this.uri + '/' + this.ns + '/ror/createInstance/' + name;
  45
+  var body  = {"args":args, "kwargs":kwargs};
  46
+  return JSON.parse(doRequest("POST", rURI, JSON.stringify(body)));
  47
+}
  48
+RemoteNode.prototype.setAttribute = function (name, attr, obj, reference) {
  49
+  if (reference == undefined) {
  50
+    var reference = false;
  51
+  }
  52
+  var rURI = this.uri + '/' + this.ns + '/ror/setAttribute/' + name;
  53
+  var body = {"attribute":attr, "value":obj, "reference":reference};
  54
+  return JSON.parse(doRequest("POST", rURI, JSON.stringify(body)));
  55
+}
  56
+RemoteNode.prototype.setItem = function (name, attr, obj, reference) {
  57
+  if (reference == undefined) {
  58
+    var reference = false;
  59
+  }
  60
+  var rURI = this.uri + '/' + this.ns + '/ror/setItem/' + name;
  61
+  var body = {"attribute":attr, "value":obj, "reference":reference};
  62
+  return JSON.parse(doRequest("POST", rURI, JSON.stringify(body)));
  63
+}
  64
+RemoteNode.prototype.addListener = function (namespace) {
  65
+  var rURI = this.uri + '/listeners';
  66
+  return JSON.parse(doRequest("POST", rURI, JSON.stringify({"namespace":namespace})));
  67
+}
  68
+RemoteNode.prototype.fireEvent = function (e, obj) {
  69
+  var rURI = this.uri + e;
  70
+  doRequest("PUT", rURI, JSON.stringify(obj));
  71
+}
  72
+
  73
+
  74
+var EventNode = function (ns) {
  75
+  this.ns = ns;
  76
+  this.uri = null;
  77
+  this.nodes = {};
  78
+  this.objectMap = {"castileRegistry":{}};
  79
+  this.listeners = {};
  80
+}
  81
+EventNode.prototype.init = function (uri) {
  82
+  this.register(uri);
  83
+  this.uri = this.nodes[uri].uri + '/' + this.ns;
  84
+  // TODO: set this.description to a proper description object
  85
+}
  86
+EventNode.prototype.register = function (uri) {
  87
+  var obj = JSON.parse(doReqest("POST", 'TODO:: PATH TO REGISTER',
  88
+                                JSON.stringify(this.description)));
  89
+  var node = new RemoteNode(obj);
  90
+  this.nodes[uri] = node;
  91
+  this.nodes[obj.namespace] = node;
  92
+}
  93
+EventNode.prototype.add_object = function (name, obj) {
  94
+  this.objectMap[name] = obj;
  95
+}
  96
+EventNode.prototype.describe = function (name) {
  97
+  // TODO