fixbot/api.py
author terom@fixme.fi
Mon, 15 Sep 2008 00:53:59 +0300
changeset 23 67e71e9170e5
parent 21 aa6df8f9c44a
child 30 33527d91b6f6
permissions -rw-r--r--
rename plugin fixbot -> fixbot_nexus, add fixbot_logwatch plugin, fix some random bugs
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.python import log, usage
from datetime import datetime
import sys

import buffer

from api_secret import secret

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

    # module's name
    name = None

    # module's version, as a 16-bit integer
    version = None

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

    def __str__ (self) :
        return "Module %s:%d" % (self.name, self.version)
    
    def __repr__ (self) :
        return "<module %s:%d with events: %s>" % (self.name, self.version, ", ".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 == secret, "Mismatching API secrets!")

        m = ModuleInfo()
        
        m.name = i.readVarLen('B')
        m.version = i.readItem('H')

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

        self.module_name = m.name

        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('B')
        
        e = Event(self.module, event_type, event_msg)

#        log.msg("Got mod_event of %r" % (e))

        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', secret)
        o.writeVarLen('B', self.factory.name)
        o.writeItem("H", self.factory.version)
        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('B', event.msg)

        self.send(o)

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

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

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

    def __init__ (self) :
        self.connection = None

    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) :
        self.nexus = nexus

class ClientOptions (usage.Options) :
    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     ),
    ]

    optFlags = [

    ]

def makeService (client_module_factory, config) :
    s = service.MultiService()
    
    # the API client
    module_factory = client_module_factory()
    
    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'], module_factory)
    
    api_client.setServiceParent(s)

    return s