commit 583c11f6d8e12a824fb22eef347a9c7de99462f4
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Wed May 13 14:49:47 2009 +0000

    git-svn-id: http://ncclient.googlecode.com/svn/trunk@115 6dbcf712-26ac-11de-a2f3-1373824ab735

diff --git a/ncclient/transport/session.py b/ncclient/transport/session.py
index 54bf304..e43395f 100644
--- a/ncclient/transport/session.py
+++ b/ncclient/transport/session.py
@@ -15,26 +15,20 @@
 from Queue import Queue
 from threading import Thread, Lock, Event
 
+from ncclient import content
 from ncclient.capabilities import Capabilities
-from ncclient.content import parse_root
-
-from hello import HelloHandler
 
 import logging
 logger = logging.getLogger('ncclient.transport.session')
 
-
 class Session(Thread):
     
-    "TODO: docstring"
-    
     def __init__(self, capabilities):
-        "Subclass constructor should call this"
         Thread.__init__(self)
-        self.setDaemon(True)
-        self._listeners = set() # TODO(?) weakref
+        self.set_daemon(True)
+        self._listeners = set() # 3.0's weakset ideal
         self._lock = Lock()
-        self.setName('session')
+        self.set_name('session')
         self._q = Queue()
         self._client_capabilities = capabilities
         self._server_capabilities = None # yet
@@ -46,7 +40,7 @@ class Session(Thread):
     def _dispatch_message(self, raw):
         "TODO: docstring"
         try:
-            root = parse_root(raw)
+            root = content.parse_root(raw)
         except Exception as e:
             logger.error('error parsing dispatch message: %s' % e)
             return
@@ -77,7 +71,7 @@ class Session(Thread):
         # callbacks
         def ok_cb(id, capabilities):
             self._id = id
-            self._server_capabilities = Capabilities(capabilities)
+            self._server_capabilities = capabilities
             init_event.set()
         def err_cb(err):
             error[0] = err
@@ -97,36 +91,30 @@ class Session(Thread):
                      (self._id, self._server_capabilities))
     
     def add_listener(self, listener):
-        "TODO: docstring"
         logger.debug('installing listener %r' % listener)
+        if not isinstance(listener, SessionListener):
+            raise SessionError("Listener must be a SessionListener type")
         with self._lock:
             self._listeners.add(listener)
     
     def remove_listener(self, listener):
-        "TODO: docstring"
         logger.debug('discarding listener %r' % listener)
         with self._lock:
             self._listeners.discard(listener)
     
     def get_listener_instance(self, cls):
-        '''This is useful when we want to maintain one listener of a particular
-        type per subject i.e. a multiton.
-        '''
         with self._lock:
             for listener in self._listeners:
                 if isinstance(listener, cls):
                     return listener
     
     def connect(self, *args, **kwds):
-        "Subclass implements"
         raise NotImplementedError
 
     def run(self):
-        "Subclass implements"
         raise NotImplementedError
     
     def send(self, message):
-        "TODO: docstring"
         logger.debug('queueing %s' % message)
         self._q.put(message)
     
@@ -146,8 +134,65 @@ class Session(Thread):
     
     @property
     def id(self):
+        "`session-id` if session is initialized, :const:`None` otherwise"
         return self._id
     
     @property
     def can_pipeline(self):
         return True
+
+
+class SessionListener(object):
+    
+    def callback(self, root, raw):
+        raise NotImplementedError
+    
+    def errback(self, ex):
+        raise NotImplementedError
+
+
+class HelloHandler(SessionListener):
+    
+    def __init__(self, init_cb, error_cb):
+        self._init_cb = init_cb
+        self._error_cb = error_cb
+    
+    def callback(self, root, raw):
+        if content.unqualify(root[0]) == 'hello':
+            try:
+                id, capabilities = HelloHandler.parse(raw)
+            except Exception as e:
+                self._error_cb(e)
+            else:
+                self._init_cb(id, capabilities)
+    
+    def errback(self, err):
+        self._error_cb(err)
+    
+    @staticmethod
+    def build(capabilities):
+        "Given a list of capability URI's returns <hello> message XML string"
+        spec = {
+            'tag': content.qualify('hello'),
+            'subtree': [{
+                'tag': 'capabilities',
+                'subtree': # this is fun :-)
+                    [{'tag': 'capability', 'text': uri} for uri in capabilities]
+                }]
+            }
+        return content.dtree2xml(spec)
+    
+    @staticmethod
+    def parse(raw):
+        "Returns tuple of (session-id (str), capabilities (Capabilities)"
+        sid, capabilities = 0, []
+        root = content.xml2ele(raw)
+        for child in root.getchildren():
+            tag = content.unqualify(child.tag)
+            if tag == 'session-id':
+                sid = child.text
+            elif tag == 'capabilities':
+                for cap in child.getchildren():
+                    if content.unqualify(cap.tag) == 'capability':
+                        capabilities.append(cap.text)
+        return sid, Capabilities(capabilities)
