Changeset 678

Show
Ignore:
Timestamp:
06/15/08 15:37:53 (2 months ago)
Author:
sylvain
Message:

Largely refactored the microblogging example.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • oss/headstock/headstock/example/microblog/config.xml

    r675 r678  
    44    <storage type="filesystem" target="member"> 
    55      <encoding>utf-8</encoding> 
    6       <basepath>repository</basepath> 
    7       <!-- Uncomment the following line if you want to add memcached caching support --> 
     6      <basepath>repository/home/jabber.defuze.org</basepath> 
    87      <!--<cache ref="cache0" />--> 
    98    </storage> 
    109    <storage type="filesystem" target="media"> 
    1110      <encoding>utf-8</encoding> 
    12       <basepath>repository</basepath> 
     11      <basepath>repository/home/jabber.defuze.org</basepath> 
    1312    </storage> 
    1413    <cache id="cache0"> 
     
    1716      </servers> 
    1817    </cache> 
    19 <!-- 
    20     <storage type="subversion" target=""> 
    21       <repository-uri /> 
    22       <workingcopy-path /> 
    23       <username /> 
    24       <password /> 
    25     </storage> 
    26     <storage type="zodb" target=""> 
    27       <fstype /> 
    28       <fspath /> 
    29       <address /> 
    30       <top-level-node /> 
    31     </storage> 
    32     <storage type="s3" target=""> 
    33       <public-key /> 
    34       <private-key /> 
    35       <bucket-prefix /> 
    36       <encoding>utf-8</encoding> 
    37       <aws-key-lookup> 
    38         <module /> 
    39         <callable /> 
    40         <aws-keys-path /> 
    41       </aws-key-lookup> 
    42     </storage> 
    43     <storage type="dejavu" target=""> 
    44       <dbtype /> 
    45       <capitalize /> 
    46       <encoding>ISO-8859-1</encoding> 
    47       <dbconf /> 
    48     </storage> 
    49     <storage type="tarball" target=""> 
    50       <basepath /> 
    51       <compression /> 
    52       <encoding>utf-8</encoding> 
    53     </storage> 
    54 --> 
    5518  </store> 
    5619</config> 
  • oss/headstock/headstock/example/microblog/launcher.py

    r675 r678  
    22import sys 
    33import os 
    4 import socket 
    5 import threading 
    6 from optparse import OptionParser 
     4import tempfile 
    75 
    8 from cherrypy.process import bus 
    9 from cherrypy.process import plugins, servers 
     6import cherrypy 
     7from selector4cherrypy import SelectorDispatcher 
     8from openid.store import filestore 
    109 
    11 from microblog.jabber.client import Client 
    12 from microblog.web import setup_atompub 
     10from mako.template import Template 
     11from mako.lookup import TemplateLookup 
     12     
     13import microblog.web.profiletool 
     14from microblog.web.oidtool import OpenIDTool 
     15from microblog.web.application import WebApplication 
     16from microblog.web.oid import OpenIDWebApplication 
     17from microblog.web.atompub import AtomPubWebApplication 
     18from microblog.web.profile import UserProfileAtomPubWebApplication 
     19from microblog.profile.manager import ProfileManager 
     20 
     21from microblog.atompub.application import AtomPubApplication 
    1322 
    1423base_dir = os.getcwd() 
    1524 
    16 def parse_commandline(): 
    17     from optparse import OptionParser 
    18     parser = OptionParser() 
    19     parser.add_option("-d", "--xmpp-domain", dest="domain", 
    20                       help="XMPP server domain (default: localhost)") 
    21     parser.set_defaults(domain='localhost') 
    22     parser.add_option("-a", "--address", dest="address", action="store", 
    23                        help="XMPP server address (default: localhost:5222) ") 
    24     parser.set_defaults(address='localhost:5222') 
    25     parser.add_option("-u", "--username", dest="username", 
    26                       help="XMPP username", action="store") 
    27     parser.set_defaults(username=None) 
    28     parser.add_option("-p", "--password", action="store", dest="password", 
    29                       help="XMPP password. You may also be prompted for it if you do not pass this parameter") 
    30     parser.set_defaults(password=None) 
    31     parser.add_option("-r", "--register", action="store_true", dest="register", 
    32                       help="Register the user if the server supports in-band registration (default: False)") 
    33     parser.set_defaults(register=False) 
    34     parser.add_option("-t", "--usetls", dest="usetls", action="store_true", 
    35                        help="Use TLS (default: False)") 
    36     parser.set_defaults(usetls=False) 
    37     parser.add_option("-w", "--web-only", dest="webonly", action="store_true", 
    38                        help="Web server only (default: False)") 
    39     parser.set_defaults(webonly=False) 
    40     (options, args) = parser.parse_args() 
    41  
    42     return options 
    43  
    4425class Server(object): 
    4526    def __init__(self): 
    46         self.options = parse_commandline() 
    47         self.client = None 
     27        self.setup_mako() 
     28        self.setup_atompub() 
     29        self.setup_profiles() 
     30        self.setup_openid() 
     31        self.setup_web() 
     32        self.setup_cherrypy() 
    4833 
    49         atompub = setup_atompub(base_dir) 
    50         if not self.options.webonly: 
    51             if not self.options.password: 
    52                 from getpass import getpass 
    53                 self.options.password = getpass() 
    54             host, port = self.options.address.split(':') 
    55             self.client = Client(atompub, unicode(self.options.username),  
    56                                  unicode(self.options.password),  
    57                                  unicode(self.options.domain), 
    58                                  server=host, port=int(port), 
    59                                  usetls=self.options.usetls, 
    60                                  register=self.options.register) 
     34    def run(self): 
     35        cherrypy.engine.start() 
     36        cherrypy.engine.block() 
    6137 
    62     def start(self): 
    63         if self.client: 
    64             self.client.activate() 
    65     start.priority = 90 
     38    def setup_cherrypy(self):    
     39        cherrypy.config.update({'engine.autoreload_on' : False, 
     40                                'server.socket_port' : 8080,  
     41                                'server.socket_host': '127.0.0.1', 
     42                                'server.socket_queue_size': 15, 
     43                                'log.screen': True, 
     44                                'log.access_file': os.path.join(base_dir, 'logs', 'http_access.log'), 
     45                                'log.error_file': os.path.join(base_dir, 'logs', 'http_error.log'), 
     46                                'checker.on': False,}) 
     47        d = SelectorDispatcher() 
     48        d.add('/service[/]', GET=self.atompubapp.service_get,  
     49              HEAD=self.atompubapp.service_head) 
    6650 
    67     def stop(self): 
    68         if self.client: 
    69             self.client.shutdown() 
     51        # OpenID controllers 
     52        d.add('/auth/login', GET=self.oidapp.login) 
     53        d.add('/auth/logout', GET=self.oidapp.logout) 
     54        d.add('/auth/failure', GET=self.oidapp.failure) 
     55        d.add('/auth/error', GET=self.oidapp.error) 
     56        d.add('/auth/cancelled', GET=self.oidapp.cancelled) 
    7057 
    71     def exit(self): 
    72         self.stop() 
     58        # New profiles controllers 
     59        d.add('/profile/new/feed', GET=self.newprofileapp.feed) 
     60        d.add('/profile/new/', POST=self.newprofileapp.create) 
     61        d.add('/profile/new/{id}', GET=self.newprofileapp.retrieve,  
     62              HEAD=self.newprofileapp.retrieve_head, 
     63              PUT=self.newprofileapp.replace, 
     64              DELETE=self.newprofileapp.remove) 
    7365 
    74 def setup(server): 
    75     plugins.SignalHandler(bus) 
    76     bus.subscribe('start', server.start) 
    77     bus.subscribe('graceful', server.stop) 
    78     bus.subscribe('exit', server.exit) 
     66        # Profiles controllers 
     67        d.add('/profile/feed', GET=self.existingprofileapp.feed) 
     68        d.add('/profile/', POST=self.existingprofileapp.create) 
     69        d.add('/profile/{id}', GET=self.existingprofileapp.retrieve, 
     70              HEAD=self.existingprofileapp.retrieve_head, 
     71              PUT=self.existingprofileapp.replace, 
     72              DELETE=self.existingprofileapp.remove) 
    7973 
    80 def serve_http_only(): 
    81     import cherrypy 
    82     cherrypy.quickstart() 
     74        # Main web application controllers 
     75        d.add('/signin[/]', GET=self.webapp.signin) 
     76        d.add('/signup[/]', GET=self.webapp.signup) 
     77        d.add('/signup/complete', POST=self.webapp.signup_complete) 
     78        d.add('/signout[/]', GET=self.webapp.signout) 
     79        d.add('/', GET=self.webapp.index) 
    8380 
    84 def serve(): 
    85     bus.start() 
    86     try: 
    87         try: 
    88             from Axon.Scheduler import scheduler  
    89             scheduler.run.runThreads(slowmo=0.01)  
    90         except (KeyboardInterrupt, IOError): 
    91             pass 
    92         except SystemExit: 
    93             pass 
    94     finally: 
    95         bus.exit() 
     81        cherrypy.tree.mount(self.webapp, '/', {'/': { 'request.dispatch': d, 
     82                                                      'tools.etags.on': True, 
     83                                                      'tools.etags.autotags': True, 
     84                                                      'tools.sessions.on': True, 
     85                                                      'tools.sessions.storage_type': 'memcached',}, 
     86                                               '/signup': {'tools.openid.on': True,}, 
     87                                               '/profile/feed': {'tools.openid.on': False, 
     88                                                                 'tools.etags.on': True, 
     89                                                                 'tools.etags.autotags': True,}, 
     90                                               '/profile/new': {'tools.openid.on': False, 
     91                                                                'tools.etags.on': True, 
     92                                                                'tools.etags.autotags': False,}, 
     93                                               '/js': {'tools.openid.on': False, 
     94                                                       'tools.staticdir.on': True, 
     95                                                       'tools.staticdir.dir': os.path.join(base_dir, 'design',  
     96                                                                                            'default', 'js')}, 
     97                                               '/images': {'tools.openid.on': False, 
     98                                                           'tools.staticdir.on': True, 
     99                                                           'tools.staticdir.dir': os.path.join(base_dir, 'design',  
     100                                                                                               'default', 'images')}, 
     101                                               '/css': {'tools.openid.on': False, 
     102                                                        'tools.staticdir.on': True, 
     103                                                        'tools.staticdir.dir': os.path.join(base_dir, 'design',  
     104                                                                                            'default', 'css')}}) 
     105 
     106    def setup_web(self): 
     107        self.webapp = WebApplication(base_dir, self.atompub, self.tpl_lookup) 
     108        self.oidapp = OpenIDWebApplication(self.tpl_lookup) 
     109        self.atompubapp = AtomPubWebApplication(base_dir, self.atompub, self.tpl_lookup) 
     110         
     111        collection = self.atompub.service.get_collection_by_xml_id('collection-profile') 
     112        self.existingprofileapp = UserProfileAtomPubWebApplication(base_dir, self.atompub,  
     113                                                                   collection, self.tpl_lookup) 
     114        collection = self.atompub.service.get_collection_by_xml_id('collection-profile-new') 
     115        self.newprofileapp = UserProfileAtomPubWebApplication(base_dir, self.atompub,  
     116                                                              collection, self.tpl_lookup) 
     117        self.webapp.new_profiles_atompub_app = self.newprofileapp 
     118        self.webapp.profiles_atompub_app = self.newprofileapp 
     119 
     120    def setup_profiles(self): 
     121        self.profiles = ProfileManager.load_profiles(base_dir, self.atompub) 
     122 
     123    def setup_atompub(self): 
     124        self.atompub = AtomPubApplication(base_dir) 
     125         
     126    def setup_openid(self): 
     127        store = filestore.FileOpenIDStore(tempfile.gettempdir()) 
     128        cherrypy.tools.openid = OpenIDTool(store, '/auth') 
     129 
     130    def setup_mako(self): 
     131        tpl_directory = os.path.join(base_dir, 'design', 'default', 'templates') 
     132        tpl_cache_directory = os.path.join(tpl_directory, 'cache') 
     133         
     134        self.tpl_lookup = TemplateLookup(directories=[tpl_directory],  
     135                                         module_directory=tpl_cache_directory,  
     136                                         collection_size=70) 
     137 
    96138 
    97139if __name__ == '__main__': 
    98140    s = Server() 
    99     setup(s) 
    100     if s.options.webonly: 
    101         serve_http_only() 
    102     else: 
    103         serve() 
     141    s.run() 
  • oss/headstock/headstock/example/microblog/microblog/jabber/__init__.py

    r675 r678  
     1# -*- coding: utf-8 -*- 
  • oss/headstock/headstock/example/microblog/microblog/jabber/client.py

    r675 r678  
    2727 
    2828""" 
     29from Axon.Ipc import * 
    2930from Axon.Component import component 
     31from Axon.Ipc import shutdownMicroprocess, producerFinished 
    3032from Kamaelia.Chassis.Graphline import Graphline 
    3133from Kamaelia.Chassis.Pipeline import Pipeline 
     
    3335from Kamaelia.Util.Backplane import PublishTo, SubscribeTo 
    3436from Kamaelia.Internet.TCPClient import TCPClient 
    35 from Kamaelia.Util.Console import ConsoleReader 
    36 from Axon.Ipc import shutdownMicroprocess, producerFinished 
     37from Kamaelia.Protocol.HTTP.HTTPClient import SimpleHTTPClient 
    3738     
    3839from headstock.protocol.core.stream import ClientStream, StreamError, SaslError 
     
    4546from headstock.protocol.extension.discovery import FeaturesDiscovery 
    4647from headstock.protocol.extension.pubsub import * 
     48from headstock.api import Entity 
    4749from headstock.api.jid import JID 
    48 from headstock.api.im import Message, Body, Event 
     50from headstock.api.im import Message, Body, Event, XHTMLBody 
    4951from headstock.api.contact import Presence, Roster, Item 
    50 from headstock.api import Entity 
    5152from headstock.api.activity import Activity 
    5253from headstock.api.registration import Registration 
    5354from headstock.lib.parser import XMLIncrParser 
    5455from headstock.lib.logger import Logger 
    55 from headstock.lib.utils import generate_unique 
     56from headstock.lib.utils import generate_unique, remove_BOM 
    5657 
    5758from bridge import Element as E 
    5859from bridge.common import XMPP_CLIENT_NS, XMPP_ROSTER_NS, \ 
    5960    XMPP_LAST_NS, XMPP_DISCO_INFO_NS, XMPP_IBR_NS, \ 
    60     XMPP_DISCO_ITEMS_NS, XMPP_PUBSUB_NS, XMPP_PUBSUB_EVENT_NS 
     61    XMPP_DISCO_ITEMS_NS, XMPP_PUBSUB_NS, XMPP_PUBSUB_EVENT_NS,\ 
     62    XMPP_PUBSUB_OWNER_NS 
    6163 
    6264from microblog.jabber.pubsub import DiscoHandler, ItemsHandler, MessageHandler 
     
    7779                "activity"    : "headstock.api.activity.Activity instance to send to the server"} 
    7880 
    79     def __init__(self, from_jid): 
     81    def __init__(self, from_jid, session_id): 
    8082        super(RosterHandler, self).__init__()  
    8183        self.from_jid = from_jid 
    8284        self.roster = None 
     85        self.session_id = session_id 
    8386 
    8487    def initComponents(self): 
     
    8689        # that will inform us when the server has 
    8790        # returned the per-session jid 
    88         sub = SubscribeTo("JID"
     91        sub = SubscribeTo("JID.%s" % self.session_id
    8992        self.link((sub, 'outbox'), (self, 'jid')) 
    9093        self.addChildren(sub) 
     
    116119                roster = self.recv("inbox") 
    117120                self.roster = roster 
    118                 print "Your contacts:" 
    119121                for nodeid in roster.items: 
    120122                    contact = roster.items[nodeid] 
    121                     print "  ", contact.jid 
    122123                     
    123124            if self.dataReady('ask-activity'): 
     
    141142     
    142143    Outboxes = {"outbox"  : "headstock.api.im.Message to send to the client", 
    143                 "signal"  : "Shutdown signal"} 
    144  
    145     def __init__(self): 
     144                "signal"  : "Shutdown signal", 
     145                "PI"      : "Publish item", 
     146                "DI"      : "Retract item", 
     147                "PN"      : "Purge node from items", 
     148                "CN"      : "Create node", 
     149                "DN"      : "Delete node", 
     150                "CCN"     : "Create collection node", 
     151                "DCN"     : "Delete collection node", 
     152                "SN"      : "Subscribe to node", 
     153                "UN"      : "Unsubscribe from node"} 
     154 
     155    def __init__(self, session_id, profile): 
    146156        super(DummyMessageHandler, self).__init__()  
    147157        self.from_jid = None 
     158        self.session_id = session_id 
     159        self.profile = profile 
    148160 
    149161    def initComponents(self): 
    150         sub = SubscribeTo("JID"
     162        sub = SubscribeTo("JID.%s" % self.session_id
    151163        self.link((sub, 'outbox'), (self, 'jid')) 
    152         self.addChildren(sub) 
    153         sub.activate() 
    154  
    155         sub = SubscribeTo("CONSOLE") 
    156         self.link((sub, 'outbox'), (self, 'inbox')) 
    157164        self.addChildren(sub) 
    158165        sub.activate() 
     
    175182            if self.dataReady("inbox"): 
    176183                m = self.recv("inbox") 
    177                 # in this first case, we want to send the message 
    178                 # typed in the console. 
    179                 # The message is of the form: 
    180                 #    contant_jid message 
    181184                if isinstance(m, str) and m != '': 
    182                     try: 
    183                         contact_jid, message = m.split(' ', 1) 
    184                     except ValueError: 
    185                         print "Messages format: contact_jid message" 
    186                         continue 
    187                     m = Message(unicode(self.from_jid), unicode(contact_jid),  
    188                                 type=u'chat', stanza_id=generate_unique()) 
    189                     m.event = Event.composing # note the composing event status 
    190                     m.bodies.append(Body(unicode(message))) 
    191                     self.send(m, "outbox") 
    192                      
    193                     # Right after we sent the first message 
    194                     # we send another one reseting the event status 
    195                     m = Message(unicode(self.from_jid), unicode(contact_jid),  
    196                                 type=u'chat', stanza_id=generate_unique()) 
    197                     self.send(m, "outbox") 
     185                    pass 
    198186                # In this case we actually received a message 
    199187                # from a contact, we print it. 
    200188                elif isinstance(m, Message): 
    201189                    for body in m.bodies: 
    202                         print m.from_jid, ": ", str(body) 
     190                        message = remove_BOM(body.plain_body).strip() 
     191                        print repr(message) 
     192                        if message == 'help': 
     193                            m = Message(self.from_jid, m.from_jid) 
     194                            b = """<ul> 
     195                                        <li>PI text</li> 
     196                                        <li>PI text</li> 
     197                                        <li>DI text</li> 
     198                                        <li>CN text</li> 
     199                                        <li>DN text</li> 
     200                                        <li>PN text</li> 
     201                                        <li>SN text</li> 
     202                                        <li>UN text</li> 
     203                                   </ul>""" 
     204                            b = E.load(b).xml_root 
     205                            m.bodies.append(XHTMLBody(b)) 
     206                            self.send(m, 'outbox') 
     207                        else: 
     208                            try: 
     209                                action, data = message.split(' ', 1) 
     210                            except ValueError: 
     211                                action = 'PI' 
     212                                data = message 
     213                                 
     214                            if action in self.outboxes: 
     215                                self.send(data, action)  
    203216 
    204217            if not self.anyReady(): 
     
    220233                } 
    221234 
    222     def __init__(self): 
     235    def __init__(self, session_id): 
    223236        super(ActivityHandler, self).__init__()  
     237        self.session_id = session_id 
    224238 
    225239    def initComponents(self): 
    226         sub = SubscribeTo("DISCO_FEAT"
     240        sub = SubscribeTo("DISCO_FEAT.%s" % self.session_id
    227241        self.link((sub, 'outbox'), (self, 'inbox')) 
    228242        self.addChildren(sub) 
     
    244258                disco = self.recv("inbox") 
    245259                support = disco.has_feature(XMPP_LAST_NS) 
    246                 print "Activity support: ", support 
    247260                if support: 
    248261                    self.send('', "activity-supported") 
     
    256269    Inboxes = {"inbox"       : "headstock.api.contact.Presence instance", 
    257270               "control"     : "Shutdown the client stream", 
     271               "jid"      : "headstock.api.jid.JID instance received from the server", 
    258272               "subscribe"   : "", 
    259273               "unsubscribe" : "",} 
     
    264278                "log"    : "log",} 
    265279     
    266     def __init__(self): 
     280    def __init__(self, session_id): 
    267281        super(PresenceHandler, self).__init__() 
     282        self.session_id = session_id 
     283 
     284    def initComponents(self): 
     285        sub = SubscribeTo("JID.%s" % self.session_id) 
     286        self.link((sub, 'outbox'), (self, 'jid')) 
     287        self.addChildren(sub) 
     288        sub.activate() 
     289 
     290        return 1 
    268291 
    269292    def main(self): 
     293        yield self.initComponents() 
     294 
    270295        while 1: 
    271296            if self.dataReady("control"): 
     
    275300                    self.send(producerFinished(), "signal") 
    276301                    break 
     302 
     303            if self.dataReady("jid"): 
     304                self.from_jid = self.recv('jid') 
     305 
     306                if '.microblogging' not in unicode(self.from_jid): 
     307                     sibling = JID(unicode('%s.microblogging' % self.from_jid.node), 
     308                                   self.from_jid.domain) 
     309                     p = Presence(from_jid=self.from_jid, to_jid=unicode(sibling), 
     310                                  type=u'subscribe') 
     311                     self.send(p, "outbox") 
    277312 
    278313            if self.dataReady("subscribe"): 
     
    325360    Inboxes = {"inbox"   : "headstock.api.registration.Registration", 
    326361               "error"   : "headstock.api.registration.Registration", 
    327                "control" : "Shutdown the client stream",} 
     362               "control" : "Shutdown the client stream", 
     363               "_response": ""} 
    328364     
    329365    Outboxes = {"outbox" : "headstock.api.registration.Registration", 
    330366                "signal" : "Shutdown signal", 
    331                 "log"    : "log",} 
    332      
    333     def __init__(self, username, password): 
     367                "log"    : "log", 
     368               "_request": ""} 
     369     
     370    def __init__(self, username, password, session_id, profile): 
    334371        super(RegistrationHandler, self).__init__() 
    335372        self.username = username 
    336373        self.password = password 
     374        self.profile = profile 
    337375        self.registration_id = None 
     376        self.session_id = session_id 
     377 
     378    def initComponents(self): 
     379        self.client = SimpleHTTPClient() 
     380        self.addChildren(self.client) 
     381        self.link((self, '_request'), (self.client, 'inbox'))  
     382        self.link((self.client, 'outbox'), (self, '_response'))  
     383        self.client.activate() 
    338384 
    339385    def main(self): 
     386        yield self.initComponents() 
     387 
    340388        while 1: 
    341389            if self.dataReady("control"): 
     
    346394                    break 
    347395 
     396            if self.dataReady("_response"): 
     397                self.recv("_response") 
     398                 
    348399            if self.dataReady("inbox"): 
    349400                r = self.recv('inbox') 
    350401                if r.registered: 
    351                     print "'%s' is already a registered username." % self.username 
     402                    self.send("'%s' is already a registered username." % r.infos[u'username'], 'log') 
     403                    c = Client.Sessions[r.infos[u'username']] 
     404                    c.shutdown() 
     405                    del Client.Sessions[r.infos[u'username']] 
    352406                elif self.registration_id == r.stanza_id: 
    353                     print "'%s' is now a registered user."\ 
    354                         "Please restart the client without the register flag." % self.username 
     407                    c = Client.Sessions[r.infos[u'username']] 
     408                    c.shutdown() 
     409                    del Client.Sessions[r.infos[u'username']] 
     410 
     411                    if '.microblogging' not in r.infos[u'username']: 
     412                        body = self.profile.xml() 
     413                        params = {'url': 'http://localhost:8080/profile/', 
     414                                  'method': 'POST', 'postbody': body,  
     415                                  'extraheaders': {'content-type': 'application/xml', 
     416                                                   'slug': self.profile.username, 
     417                                                   'content-length': len(body)}} 
     418                        self.send(params, '_request')  
    355419                else: 
    356420                    if 'username' in r.infos and 'password' in r.infos: 
     
    363427            if self.dataReady("error"): 
    364428                r = self.recv('error') 
    365                 print r.error 
     429                if r.error.code == '409': 
     430                    self.send("'%s' is already a registered username." % r.infos[u'username'], 'log') 
     431                    c = Client.Sessions[r.infos[u'username']] 
     432                    c.shutdown() 
     433                    del Client.Sessions[r.infos[u'username']] 
     434                self.send(r.error, 'log') 
    366435 
    367436            if not self.anyReady(): 
     
    374443               "jid"        : "", 
    375444               "streamfeat" : "", 
     445               "connected"  : "", 
     446               "unhandled"  : "", 
    376447               "control"    : "Shutdown the client stream"} 
    377448     
     
    383454                "doregistration" : ""} 
    384455 
     456    Domain = None 
     457    Host = u'localhost' 
     458    Port = 5222 
     459 
     460    Sessions = {} 
     461 
    385462    def __init__(self, atompub, username, password, domain, resource=u"headstock-client1",  
    386                  server=u'localhost', port=5222, usetls=False, register=False): 
     463                 server=u'localhost', port=5222, usetls=False, register=False, session_id=None, profile=None): 
    387464        super(Client, self).__init__()  
     465        self.running = False 
     466        self.connected = False 
    388467        self.atompub = atompub 
    389         self.jid = JID(username, domain, resource) 
     468        if not session_id: 
     469            session_id = generate_unique() 
     470        self.session_id = session_id 
     471        self.backplanes = [] 
    390472        self.username = username 
    391473        self.password = password 
     474        self.jid = JID(self.username, domain, '%s!%s' % (resource, session_id)) 
    392475        self.server = server 
    393476        self.port = port 
     
    397480        self.usetls = usetls 
    398481        self.register = register 
     482        self.restartable = False 
     483        self.profile = profile 
     484 
     485    @staticmethod 
     486    def start_clients(atompub, users): 
     487        for username, password in users: 
     488            profile = atompub.load_profile(username) 
     489            Client.connect_jabber_user(atompub, username, password, profile) 
     490 
     491    @staticmethod 
     492    def register_jabber_user(atompub, username, password, profile): 
     493        c = Client(atompub, unicode(username), unicode(password),  
     494                   domain=Client.Domain, server=Client.Host, port=Client.Port, 
     495                   usetls=True, register=True, profile=profile) 
     496        Client.Sessions[c.username] = c 
     497        c.activate() 
     498 
     499        username = unicode('%s.microblogging' % username) 
     500        c = Client(atompub, unicode(username), unicode(password),  
     501                   domain=Client.Domain, server=Client.Host, port=Client.Port, 
     502                   usetls=True, register=True, profile=profile) 
     503        Client.Sessions[c.username] = c 
     504        c.activate() 
     505 
     506    @staticmethod 
     507    def connect_jabber_user(atompub, username, password, profile): 
     508        c = Client(atompub, unicode(username), unicode(password),  
     509                   domain=Client.Domain, server=Client.Host, port=Client.Port, 
     510                   usetls=True, register=False, profile=profile) 
     511        Client.Sessions[c.username] = c 
     512        c.activate() 
     513         
     514        username = unicode('%s.microblogging' % username) 
     515        c = Client(atompub, unicode(username), unicode(password),  
     516                   domain=Client.Domain, server=Client.Host, port=Client.Port, 
     517                   usetls=True, register=False, profile=profile) 
     518        Client.Sessions[c.username] = c 
     519        c.activate() 
     520 
     521    @staticmethod 
     522    def disconnect_jabber_user(username): 
     523        if username in Client.Sessions: 
     524            c = Client.Sessions[username] 
     525            del Client.Sessions[username] 
     526            c.shutdown() 
     527 
     528    @staticmethod 
     529    def get_status(username): 
     530        return username in Client.Sessions 
     531 
     532    @staticmethod 
     533    def is_registered(username): 
     534        if username.lower() in Client.Sessions: 
     535            return Client.Sessions[username.lower()] 
     536 
     537        return False 
    399538 
    400539    def passwordLookup(self, jid): 
     
    402541 
    403542    def shutdown(self): 
    404         self.send(Presence.to_element(Presence(self.jid, type=u'unavailable')), 'forward') 
     543        #self.send(Presence.to_element(Presence(self.jid, type=u'unavailable')), 'forward') 
    405544        self.send('OUTGOING : </stream:stream>', 'log') 
    406         self.send('</stream:stream>', 'outbox')  
     545        self.send('</stream:stream>', 'outbox') 
     546        self.running = False  
    407547 
    408548    def abort(self): 
    409549        self.send('OUTGOING : </stream:stream>', 'log') 
    410         self.send('</stream:stream>', 'outbox')  
     550        self.send('</stream:stream>', 'outbox') 
     551        self.running = False  
    411552 
    412553    def setup(self): 
     554        self.running = True 
     555 
    413556        # Backplanes are like a global entry points that 
    414557        # can be accessible both for publishing and 
     
    426569        # the server returns the per-session JID that 
    427570        # is of interest for most other components). 
    428         Backplane("CONSOLE").activate() 
    429         Backplane("JID").activate() 
     571        self.backplanes.append(Backplane("JID.%s" % self.session_id).activate()) 
    430572        # Used to inform components that the session is now active 
    431         Backplane("BOUND").activate(
     573        self.backplanes.append(Backplane("BOUND.%s" % self.session_id).activate()
    432574        # Used to inform components of the supported features 
    433         Backplane("DISCO_FEAT").activate(
    434  
    435         sub = SubscribeTo("JID"
     575        self.backplanes.append(Backplane("DISCO_FEAT.%s" % self.session_id).activate()
     576 
     577        sub = SubscribeTo("JID.%s" % self.session_id
    436578        self.link((sub, 'outbox'), (self, 'jid')) 
    437579        self.addChildren(sub) 
    438580        sub.activate() 
    439581 
    440         # We pipe everything typed into the console 
    441         # directly to the console backplane so that 
    442         # every components subscribed to the console 
    443         # backplane inbox will get the typed data and 
    444         # will decide it it's of concern or not. 
    445         Pipeline(ConsoleReader(), PublishTo('CONSOLE')).activate() 
     582        sub = SubscribeTo("BOUND.%s" % self.session_id) 
     583        self.link((sub, 'outbox'), (self, 'connected')) 
     584        self.addChildren(sub) 
     585        sub.activate() 
    446586 
    447587        # Add two outboxes ro the ClientSteam to support specific extensions. 
     
    453593        ClientStream.Outboxes["%s.unsubscribe" % XMPP_PUBSUB_NS] = "Pubsub unsubscription handler" 
    454594        ClientStream.Outboxes["%s.subscriptions" % XMPP_PUBSUB_NS] = "Pubsub subscriptions handler" 
     595        ClientStream.Outboxes["%s.affiliations" % XMPP_PUBSUB_NS] = "Pubsub affiliations handler" 
    455596        ClientStream.Outboxes["%s.create" % XMPP_PUBSUB_NS] = "Pubsub node creation handler" 
    456         ClientStream.Outboxes["%s.delete" % XMPP_PUBSUB_NS] = "Pubsub node deletion handler" 
     597        ClientStream.Outboxes["%s.purge" % XMPP_PUBSUB_OWNER_NS] = "Pubsub node purge handler" 
     598        ClientStream.Outboxes["%s.delete" % XMPP_PUBSUB_OWNER_NS] = "Pubsub node delete handler" 
    457599        ClientStream.Outboxes["%s.publish" % XMPP_PUBSUB_NS] = "Pubsub item publication handler" 
    458600        ClientStream.Outboxes["%s.retract" % XMPP_PUBSUB_NS] = "Pubsub item deletion handler" 
     
    461603 
    462604        self.client = ClientStream(self.jid, self.passwordLookup, use_tls=self.usetls) 
    463          
     605        self.addChildren(self.client) 
     606        self.client.activate() 
     607 
    464608        self.graph = Graphline(client = self, 
    465                                console = SubscribeTo('CONSOLE'), 
    466                                logger = Logger(path=None, stdout=True), 
     609                               logger = Logger(path='./logs/%s.log' % self.username,  
     610                                               stdout=False, name=self.session_id), 
    467611                               tcp = TCPClient(self.server, self.port), 
    468612                               xmlparser = XMLIncrParser(), 
     
    470614                               streamerr = StreamError(), 
    471615                               saslerr = SaslError(), 
    472                                discohandler = DiscoHandler(self.jid, self.atompub, self.domain), 
    473                                activityhandler = ActivityHandler(), 
    474                                rosterhandler = RosterHandler(self.jid), 
    475                                registerhandler = RegistrationHandler(self.username, self.password), 
    476                                msgdummyhandler = DummyMessageHandler(), 
    477                                presencehandler = PresenceHandler(), 
    478                                itemshandler = ItemsHandler(self.jid, self.atompub, self.domain), 
    479                                pubsubmsgeventhandler = MessageHandler(self.jid, self.atompub, self.domain), 
     616                               discohandler = DiscoHandler(self.jid, self.atompub, self.domain,  
     617                                                           session_id=self.session_id, 
     618                                                           profile=self.profile), 
     619                               activityhandler = ActivityHandler(session_id=self.session_id), 
     620                               rosterhandler = RosterHandler(self.jid, session_id=self.session_id), 
     621                               registerhandler = RegistrationHandler(self.username, self.password, 
     622