commit 35ad9d8106fb5a272258ca1c826e0830046a23ff
Author: Shikhar Bhushan <shikhar@schmizz.net>
Date:   Mon Apr 20 06:52:47 2009 +0000

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

diff --git a/ncclient/capability.py b/ncclient/capability.py
index 7d4c03f..ec0c640 100644
--- a/ncclient/capability.py
+++ b/ncclient/capability.py
@@ -34,6 +34,8 @@ class Capabilities:
             shorthand = Capabilities.guess_shorthand(uri)
         self._dict[uri] = shorthand
     
+    set = add
+    
     def remove(self, key):
         if key in self._dict:
             del self._dict[key]
@@ -42,11 +44,12 @@ class Capabilities:
                 if self._dict[uri] == key:
                     del self._dict[uri]
                     break
-    
+        
     @staticmethod
     def guess_shorthand(uri):
         if uri.startswith('urn:ietf:params:netconf:capability:'):
             return (':' + uri.split(':')[5])
+
     
 CAPABILITIES = Capabilities([
     'urn:ietf:params:netconf:base:1.0',
diff --git a/ncclient/content/__init__.py b/ncclient/content/__init__.py
index de18804..5fb5bc9 100644
--- a/ncclient/content/__init__.py
+++ b/ncclient/content/__init__.py
@@ -12,8 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from ..error import ClientError, NETCONFError
+NETCONF_NS = 'urn:ietf:params:xml:ns:netconf:base:1.0'
 
-class ContentError(ClientError): pass
-
-class ValidationError(NETCONFError): pass
diff --git a/ncclient/content/error.py b/ncclient/content/error.py
new file mode 100644
index 0000000..a98ff91
--- /dev/null
+++ b/ncclient/content/error.py
@@ -0,0 +1,18 @@
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ..error import ClientError, NETCONFError
+
+class ContentError(ClientError):
+    pass
\ No newline at end of file
diff --git a/ncclient/content/hello.py b/ncclient/content/hello.py
index a846c34..181f680 100644
--- a/ncclient/content/hello.py
+++ b/ncclient/content/hello.py
@@ -17,57 +17,21 @@ from xml.etree import cElementTree as ElementTree
 
 logging.getLogger('ncclient.content.hello')
 
+from . import NETCONF_NS
+from .util import qualify as _
 from ..capability import Capabilities
 
-ns = 'urn:ietf:params:xml:ns:netconf:base:1.0'
-
 def make(capabilities):
-    return '<hello xmlns="%s">%s</hello>' % (ns, capabilities)
+    return '<hello xmlns="%s">%s</hello>' % (NETCONF_NS, capabilities)
 
 def parse(raw):
     id, capabilities = 0, Capabilities()
-    hello = ElementTree.fromstring(raw)
-    for child in hello.getchildren():
-        if child.tag == '{%s}session-id' % ns:
-            id = child.text
-        elif child.tag == '{%s}capabilities' % ns:
-            for cap in child.getiterator('{%s}capability' % ns):
-                capabilities.add(cap.text)
+    root = ElementTree.fromstring(raw)
+    if root.tag == _('hello'):
+        for child in hello.getchildren():
+            if child.tag == _('session-id'):
+                id = int(child.text)
+            elif child.tag == _('capabilities'):
+                for cap in child.getiterator(_('capability')):
+                    capabilities.add(cap.text)
     return id, capabilities
-
-#class HelloParser:
-#    
-#    'Fast parsing with expat'
-#    
-#    capability, sid = range(2)
-#    
-#    def __init__(self, raw):
-#        self._sid = None
-#        self._capabilities = Capabilities()
-#        p = xml.parsers.expat.ParserCreate()
-#        p.StartElementHandler = self._start_element
-#        p.EndElementHandler = self._end_element
-#        p.CharacterDataHandler = self._char_data
-#        self._expect = None
-#        p.parse(raw, True)
-#    
-#    def _start_element(self, name, attrs):
-#        if name == 'capability':
-#            self._expect = HelloParser.capability
-#        elif name == 'session-id':
-#            self._expect = HelloParser.sid
-#    
-#    def _end_element(self, name):
-#        self._expect = None
-#    
-#    def _char_data(self, data):
-#        if self._expect == HelloParser.capability:
-#            self._capabilities.add(data)
-#        elif self._expect == HelloParser.sid:
-#            self._sid = int(data)
-#    
-#    @property
-#    def sid(self): return self._sid
-#    
-#    @property
-#    def capabilities(self): return self._capabilities
\ No newline at end of file
diff --git a/ncclient/content/rpc.py b/ncclient/content/rpc.py
new file mode 100644
index 0000000..b376520
--- /dev/null
+++ b/ncclient/content/rpc.py
@@ -0,0 +1,41 @@
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from xml.etree import cElementTree as ElementTree
+
+from . import NETCONF_NS
+from .util import qualify as _
+
+def make(id, op):
+    return '<rpc message-id="%s" xmlns="%s">%s</rpc>' % (id, NETCONF_NS, op)
+
+#def parse(raw):
+#    
+#    class RootElementParser:
+#        
+#        def __init__(self):
+#            self.id = 0
+#            self.is_notification = False
+#            
+#        def start(self, tag, attrib):
+#            if tag == _('rpc'):
+#                self.id = int(attrib['message-id'])
+#            elif tag == _('notification'):
+#                self.is_notification = True
+#    
+#    target = RootElementParser()
+#    parser = ElementTree.XMLTreeBuilder(target=target)
+#    parser.feed(raw)
+#    return target.id, target.is_notification
+#
diff --git a/ncclient/content/util.py b/ncclient/content/util.py
new file mode 100644
index 0000000..3e64470
--- /dev/null
+++ b/ncclient/content/util.py
@@ -0,0 +1,18 @@
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from . import NETCONF_NS
+
+def qualify(tag, ns=NETCONF_NS):
+    return '{%s}%s' % (ns, tag)
diff --git a/ncclient/listener.py b/ncclient/listener.py
index 14e1c16..695c2e0 100644
--- a/ncclient/listener.py
+++ b/ncclient/listener.py
@@ -12,39 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from threading import Lock
+from content import rpc
 
-import logging
+class SessionListener:
 
-logger = logging.getLogger('ncclient.listener')
-
-class Subject:
-        
-    def __init__(self, listeners=[]):
-        self._listeners = listeners
-        self._lock = Lock()
-    
-    def has_listener(self, listener):
-        with self._lock:
-            return (listener in self._listeners)
+    def __init__(self):
+        self._id2rpc = {}
+        self._subscription_id = None # notifications are delivered to the rpc
+                                    # that created the subscription
     
-    def add_listener(self, listener):
-        with self._lock:
-            self._listeners.append(listener)
+    def set_subscription(self, id):
+        self._subscription = id
     
-    def remove_listener(self, listener):
-        with self._lock:
-            try:
-                self._listeners.remove(listener)
-            except ValueError:
-                pass
+    def reply(self, raw):
+        id, is_notification = rpc.parse(raw)
+        if is_notification:
+            self._id2rpc[self._subscription_id].event(raw)
+        else:
+            self._id2rpc[id]._deliver(raw)
+            del self._id2rpc[id]
     
-    def dispatch(self, event, *args, **kwds):
-        with self._lock:
-            listeners = list(self._listeners)
-        for l in listeners:
-            logger.debug('dispatching [%s] to [%s]' % (event, l.__class__))
-            try:
-                getattr(l, event)(*args, **kwds)
-            except Exception as e:
-                logger.warning(e)
+    def error(self, buf):
+        pass
diff --git a/ncclient/rpc.py b/ncclient/rpc.py
index c7d83bf..a91fe65 100644
--- a/ncclient/rpc.py
+++ b/ncclient/rpc.py
@@ -12,15 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from threading import Event
+import content
 
-from listener import Listener
+from threading import Event
 
-from content import MessageIDParser
+from listener import RPCReplyListener
 
 class RPC:
     
-    cur_id = {}
+    current_id = {}
+    listeners = {}
 
     def __init__(self, session=None, async=False):
         self._session = None
@@ -33,46 +34,44 @@ class RPC:
         if self._event.isSet():
             return self._reply
     
-    def do(self, session, async=False):
+    def do(self, async=False):
         self._async = async
     
-    def deliver(self, reply):
+    def _deliver(self, reply):
         self._reply = reply
         self._event.set()
 
     @property
-    def has_reply(self): return self._event.isSet()
+    def has_reply(self):
+        return self._event.isSet()
     
     @property
-    def async(self): return self._async
+    def is_async(self):
+        return self._async
     
     @property
-    def listener(self): return self._listener
+    def listener(self):
+        if RPC.listeners[self._sid] is None:
+            RPC.listeners[self.sid] = listener.RPCReplyListener()
+        return RPC.listeners[self._sid]
+    
+    @property
+    def ok(self):
+        pass
     
     def _next_id(self):
-        cur_id[self._sid] = cur_id.get(self._sid, 0) + 1
-        return cur_id[self._sid]
+        RPC.current_id[self._session.id] = RPC.current_id.get(self._session.id, 0) + 1
+        return RPC.current_id[self._sid]
     
 class RPCReply:
     
-    def __init__(self, raw):
+    def __init__(self, id, raw):
+        self._id = id
         self._raw = raw
     
-    def get_id(self):
-        return content.rpc.parse_msg_id(raw)
-
-class RPCError(NETCONFError):
+    @property
+    def id(self):
+        return self._id
     
+class RPCError(NETCONFError):
     pass
-
-class ReplyListener(Listener):
-    
-    def __init__(self):
-        self._id2rpc = {}
-    
-    def reply(self, msg):
-        reply = RPCReply(msg)
-        id2rpc[reply.get_id()].deliver(reply)
-    
-    def error(self, buf):
-        pass
diff --git a/ncclient/session.py b/ncclient/session.py
index 949f569..77fc6b8 100644
--- a/ncclient/session.py
+++ b/ncclient/session.py
@@ -13,18 +13,19 @@
 # limitations under the License.
 
 import logging
-
 from threading import Thread, Event
 from Queue import Queue
 
-from error import ClientError
-from content import hello
-from listener import Subject
 from capability import CAPABILITIES
+from content import hello
+from error import ClientError
+from subject import Subject
 
 logger = logging.getLogger('ncclient.session')
 
-class SessionError(ClientError): pass
+class SessionError(ClientError):
+    
+    pass
 
 class Session(Thread, Subject):
     
@@ -39,22 +40,6 @@ class Session(Thread, Subject):
         self._init_event = Event()
         self._q = Queue()
     
-    def _close(self):
-        self._connected = False
-    
-    def _init(self):
-        self._connected = True
-        # start the subclass' main loop
-        self.start()
-        # queue client's hello message for sending
-        self.send(hello.make(self._client_capabilities))
-        # we expect server's hello message, wait for _init_event to be set by HelloListener
-        self._init_event.wait()
-        # there may have been an error
-        if self._error:
-            self._close()
-            raise self._error
-
     def connect(self):
         raise NotImplementedError
 
@@ -69,16 +54,20 @@ class Session(Thread, Subject):
     ### Properties
 
     @property
-    def client_capabilities(self): return self._client_capabilities
+    def client_capabilities(self):
+        return self._client_capabilities
     
     @property
-    def serve_capabilities(self): return self._server_capabilities
+    def serve_capabilities(self):
+        return self._server_capabilities
     
     @property
-    def connected(self): return self._connected
+    def connected(self):
+        return self._connected
     
     @property
-    def id(self): return self._id    
+    def id(self):
+        return self._id    
 
     class HelloListener:
         
@@ -106,3 +95,21 @@ class Session(Thread, Subject):
         
         def close(self, err):
             self._done(err)
+    
+    ### Methods for which subclasses should call super after they are done
+    
+    def _connect(self):
+        self._connected = True
+        # start the subclass' main loop
+        self.start()
+        # queue client's hello message for sending
+        self.send(hello.make(self._client_capabilities))
+        # we expect server's hello message, wait for _init_event to be set by HelloListener
+        self._init_event.wait()
+        # there may have been an error
+        if self._error:
+            self._close()
+            raise self._error
+    
+    def _close(self):
+        self._connected = False
diff --git a/ncclient/ssh.py b/ncclient/ssh.py
index 43500d8..77d9527 100644
--- a/ncclient/ssh.py
+++ b/ncclient/ssh.py
@@ -62,11 +62,7 @@ class SSHSession(Session):
         self._channel = transport.open_session()
         self._channel.invoke_subsystem('netconf')
         self._channel.set_name('netconf')
-        self._init()
-
-    def _close(self):
-        self._channel.close()
-        Session._close(self)
+        self._connect()
     
     def run(self):
         
@@ -105,6 +101,11 @@ class SSHSession(Session):
         
         logger.debug('** broke out of main loop **')
         self.dispatch('close', SessionCloseError(self._in_buf, self._out_buf))
+    
+    def _close(self):
+        self._channel.close()
+        Session._close(self)
+
 
 class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
     
@@ -113,4 +114,4 @@ class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
     
     def missing_host_key(self, client, hostname, key):
         if not self._cb(hostname, key):
-            raise SSHError
\ No newline at end of file
+            raise SSHError
diff --git a/ncclient/subject.py b/ncclient/subject.py
new file mode 100644
index 0000000..32012f8
--- /dev/null
+++ b/ncclient/subject.py
@@ -0,0 +1,64 @@
+# Copyright 2009 Shikhar Bhushan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from threading import Lock
+
+import logging
+
+logger = logging.getLogger('ncclient.listener')
+
+class Subject:
+        
+    def __init__(self, listeners=[]):
+        self._listeners = listeners
+        self._lock = Lock()
+    
+    def has_listener(self, listener):
+        with self._lock:
+            return (listener in self._listeners)
+    
+    def add_listener(self, listener):
+        with self._lock:
+            self._listeners.append(listener)
+    
+    def remove_listener(self, listener):
+        with self._lock:
+            try:
+                self._listeners.remove(listener)
+            except ValueError:
+                pass
+    
+    def dispatch(self, event, *args, **kwds):
+        with self._lock:
+            listeners = list(self._listeners)
+        for l in listeners:
+            logger.debug('dispatching [%s] to [%s]' % (event, l.__class__))
+            try:
+                getattr(l, event)(*args, **kwds)
+            except Exception as e:
+                logger.warning(e)
+
+
+class SessionListener:
+
+    def __init__(self):
+        self._id2rpc = {}
+        self._subscription = None
+
+    def reply(self, raw):
+        reply = RPCReply(msg)
+        id2rpc[reply.id]._deliver(reply)
+    
+    def error(self, buf):
+        pass
