fixbot/api.py
author Tero Marttila <terom@fixme.fi>
Thu, 04 Feb 2010 20:10:34 +0200
changeset 44 779d7cd38f1a
parent 39 e82b6df5baa3
child 58 31a17b0b5159
permissions -rw-r--r--
remove api.Module.version
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.python import log, usage
from datetime import datetime
import sys

from fixbot import buffer, config

class ModuleInfo (object) :
    """
        Some info about a module
    """

    # module's name
    name = None

    # list of valid event types (strings)
    event_types = None

    def __str__ (self) :
        return "Module %s:" % (self.name)
    
    def __repr__ (self) :
        return "<module %s with events: %s>" % (self.name, ", ".join(self.event_types))

class Event (object) :
    # the ModuleInfo object
    module = None

    # the event type as a string
    type = None

    # event message as a string (under 255 bytes in length!)
    msg = None

    # timestamp as a datetime.datetime
    when = None

    def __init__ (self, module, type, msg) :
        assert type in module.event_types, "Invalid event-type %s for %r" % (type, self.module)
        
        self.module = module
        self.type = type
        self.msg = msg

        self.when = datetime.now()
    
    def __str__ (self) :
        return "[%s] %s" % (self.type, self.msg)
    
    def __repr__ (self) :
        return "%s @ %s" % (self.type, self.when)
 
CLIENT_COMMANDS = [
    "module_init",
    "module_event",
]

SERVER_COMMANDS = [
    "module_ok",
]
       
class ServerProtocol (buffer.StreamProtocol, protocol.Protocol) :
    RECV_COMMANDS = CLIENT_COMMANDS
    SEND_COMMANDS = SERVER_COMMANDS    

    VALID_STATES = [
        "wait_init",
        "wait_event"
    ]
    
    # proto state
    state = None

    # module info
    module = None
    
    def _assert (self, condition, msg) :
        if not condition :
            self.transport.loseConnection()
            log.err("assert failed in APIProtocol for %s: %s" % (self.module, msg))
    
    def connectionMade (self) :
        log.msg("Client connected")

    def connectionLost (self, reason) :
        log.msg("Connection lost: %s" % reason)
        
        if self.module :
            self.factory.nexus.unregisterModule(self.module, reason.getErrorMessage())

    def on_module_init (self, i) :
        self._assert(not self.module, "module_init with non-None self.module")

        peer_secret = i.readVarLen('B')
        
        self._assert(peer_secret == self.factory.secret, "Mismatching API secrets!")

        m = ModuleInfo()
        
        m.name = i.readVarLen('B')
        m.event_types = list(buffer.readStringStream(i, 'B'))
        m.addr = self.transport.getPeer()

        log.msg("Got mod_init for %r" % m)
        
        self.factory.nexus.registerModule(m, self)

        self.module = m

        o = self.startCommand('module_ok')

        self.send(o)

    def on_module_event (self, i) :
        self._assert(self.module, "module_event with None self.module!")

        event_type = i.readEnum(self.module.event_types)
        event_msg = i.readVarLen('H')
        
        e = Event(self.module, event_type, event_msg)

        self.factory.nexus.handleEvent(e)

    def logPrefix (self) :
        if self.module :
            return str(self.module)
        else :
            return super(ServerProtocol, self).logPrefix()

class ClientProtocol (buffer.StreamProtocol, protocol.Protocol) :
    RECV_COMMANDS = SERVER_COMMANDS
    SEND_COMMANDS = CLIENT_COMMANDS

    def connectionMade (self) :
        log.msg("Connected to API server, sending module init message")

        o = self.startCommand('module_init')
        o.writeVarLen('B', self.factory.secret)
        o.writeVarLen('B', self.factory.name)
        buffer.writeStringStream(o, 'B', self.factory.event_types)

        self.send(o)

    def sendEvent (self, event) :
        o = self.startCommand('module_event')
        o.writeEnum(self.factory.event_types, event.type)
        o.writeVarLen('H', event.msg[:2**16])

        self.send(o)

    def on_module_ok (self, i) :
        log.msg("Registration OK")

        self.factory.connected(self)
    
    def logPrefix (self) :
        return "module %s client" % (self.factory.name)

class Module (ModuleInfo, protocol.ClientFactory) :
    protocol = ClientProtocol

    def __init__ (self, config) :
        self.connection = None
        self.secret = config['api-secret']

    def connected (self, connection) :
        log.msg("Connected!")
        self.connection = connection

        self.handleConnect()

    def disconnect (self) :
        self.connection.transport.loseConnection()
    
    def sendEvent (self, type, msg) :
        self.connection.sendEvent(self.buildEvent(type, msg))

    def buildEvent (self, type, msg) :
        return Event(self, type, msg)

    def handleConnect (self) :
        """
            Do something
        """

        pass

class ServerFactory (protocol.ServerFactory) :
    protocol = ServerProtocol

    def __init__ (self, nexus, secret) :
        self.nexus = nexus
        self.secret = secret

class ClientOptions (config.ConfigOptions) :
    optParameters = [
        (   "api-server",   "s",    "127.0.0.1",        "address of API server to connect to"           ),
        (   "api-port",     "P",    34888,              "port of API server to connect to",     int     ),
        (   "api-secret",   None,   None,               "secret key for API connections"                ),
    ]

    optFlags = [

    ]

def makeService (module_class, config) :
    s = service.MultiService()

    # build factory
    factory = module_class(config)
    
    # the API client
    log.msg("Connecting to API server on [%s:%d]" % (config['api-server'], config['api-port']))
    api_client = internet.TCPClient(config['api-server'], config['api-port'], factory)
    
    api_client.setServiceParent(s)

    return s