""" @file event_queue.py @date 2009-02-09 Contributors can be viewed at: http://svn.secondlife.com/svn/linden/projects/2008/pyogp/CONTRIBUTORS.txt $LicenseInfo:firstyear=2008&license=apachev2$ Copyright 2008, Linden Research, Inc. Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 or in http://svn.secondlife.com/svn/linden/projects/2008/pyogp/LICENSE.txt $/LicenseInfo$ """ # standard python libs from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG # related from eventlet import api, util # the following makes socket calls nonblocking. magic util.wrap_socket_with_coroutine_socket() # pyogp from pyogp.lib.base.utilities.events import Event from pyogp.lib.base.groups import ChatterBoxInvitation_Message # messaging from pyogp.lib.base.message.packet import UDPPacket from pyogp.lib.base.message.template_dict import TemplateDictionary from pyogp.lib.base.message.template import MsgData, MsgBlockData, MsgVariableData # initialize logging logger = getLogger('pyogp.lib.base.event_queue') log = logger.log class EventQueueClient(object): """ handles an event queue of either an agent domain or a simulator Initialize the event queue client class >>> client = EventQueueClient() The event queue client requires an event queue capability >>> from pyogp.lib.base.caps import Capability >>> cap = Capability('EventQueue', http://localhost:12345/cap) >>> event_queue = EventQueueClient(cap) >>> event_queue.start() Sample implementations: region.py Tests: tests/test_event_queue.py """ def __init__(self, capability = None, settings = None, packet_handler = None, region = None, event_queue_handler = None): """ set up the event queue attributes """ # allow the settings to be passed in # otherwise, grab the defaults if settings != None: self.settings = settings else: from pyogp.lib.base.settings import Settings self.settings = Settings() # allow the packet_handler to be passed in # otherwise, grab the defaults if packet_handler != None: self.packet_handler = packet_handler elif self.settings.HANDLE_PACKETS: from pyogp.lib.base.message.packethandler import PacketHandler self.packet_handler = PacketHandler(settings = self.settings) # allow the event_queue_handler to be passed in # otherwise, grab the defaults if event_queue_handler != None: self.event_queue_handler = event_queue_handler elif self.settings.HANDLE_EVENT_QUEUE_DATA: self.event_queue_handler = EventQueueHandler(settings = self.settings) self.region = region self.cap = capability #self.type = eq_type # specify 'agentdomain' or 'region' self._running = False # this class controls this value self.stop = False # client can pause the event queue self.last_id = -1 # if applicable, initialize data to post to the event queue self.data = {} # stores the result of the post to the event queue capability self.result = None # enables proper packet parsing in event queue responses self.template_dict = TemplateDictionary() self.current_template = None def start(self): try: if self.cap.name == 'event_queue': api.spawn(self._processADEventQueue) return True elif self.cap.name == 'EventQueueGet': api.spawn(self._processRegionEventQueue) return True else: # ToDo handle this as an exception log(WARNING, 'Unable to start event queue polling due to %s' % ('unknown queue type')) return False except: return False def stop(self): log(INFO, "Stopping %s event queue." % (self.type)) self.stop = True # ToDo: turn this into a timeout for i in range(1,10): if self._running == False: return True # well, we failed to stop. let's log it and get outta here log(WARNING, "Failed to stop %s event queue." % (self.type)) self.stop = False return False def _handle(self, data): """ essentially a case statement to pass packets to event notifiers in the form of self attributes """ try: # Handle the event queue result if we have subscribers if len(self.subscribers) > 0: log(DEBUG, 'Handling event queue results') handler(data) except AttributeError: #log(INFO, "Received an unhandled packet: %s" % (packet.name)) pass def _processRegionEventQueue(self): if self.cap.name != 'EventQueueGet': raise exc.RegionCapNotAvailable('EventQueueGet') # well then get it...? else: self._running = True while self.stop != True: api.sleep(self.settings.REGION_EVENT_QUEUE_POLL_INTERVAL) if self.last_id != -1: self.data = {'ack':self.last_id, 'done':True} if self.settings.ENABLE_EQ_LOGGING: log(DEBUG, 'Posting to the event queue: %s' % (self.data)) try: self.result = self.cap.POST(self.data) except Exception, error: log(INFO, "Received an error we ought not care about: %s" % (error)) pass if self.result != None: self.last_id = self.result['id'] else: self.last_id = -1 self._parse_result(self.result) self._running = False log(DEBUG, "Stopped event queue processing for %s" % (self.region.SimName)) def _processADEventQueue(self): if self.cap.name != 'event_queue': raise RegionCapNotAvailable('event_queue') # change the exception here (add a new one) else: self._running = True while not self.stop: api.sleep(self.settings.agentdomain_event_queue_interval) self.result = self.capabilities['event_queue'].POST(self.data) if self.result != None: self.last_id = self.result['id'] self._parse_result(self.result) self._running = False def _parse_result(self, data): # if there are subscribers to the event queue and packet handling is enabled if self.settings.HANDLE_PACKETS: # and (len(self.handler) > 0): try: if data != None: if self.settings.ENABLE_EQ_LOGGING: log(DEBUG, 'Event Queue result: %s' % (data)) # if we are handling packets, handle the packet so any subscribers can get the data if self.settings.HANDLE_PACKETS: # this returns packets parsed_data = self._decode_eq_result(data) for packet in parsed_data: self.packet_handler._handle(packet) except Exception, error: log(WARNING, "Error parsing even queue results, data was: %s" % (data)) def _decode_eq_result(self, data=None): """ parse the event queue data, return a list of packets the building of packets borrows absurdly much from UDPDeserializer.__decode_data() """ # ToDo: this is returning packets, but perhaps we want to return packet class instances? if data != None: packets = [] if data.has_key('events'): for i in data: if i == 'id': last_id = data[i] else: for message in data[i]: # move this to a proper solution, for now, append to some list eq events # or some dict mapping name to action to take if message['message'] == 'ChatterBoxInvitation': message['name'] = message['message'] group_chat = ChatterBoxInvitation_Message(ChatterBoxInvitation_Data = message['body']) self.event_queue_handler._handle(group_chat) else: # this is a UDP packet sent over the event queue self.current_template = self.template_dict.get_template(message['message']) message_data = MsgData(self.current_template.name) # treat this like a UDPPacket # in some cases, we will be creating UDPPacket instances # where there is no corresponding entry in the message_template # hence the try: except: # there must be a better solution packet = UDPPacket(message_data) packet.name = self.current_template.name # irrelevant packet attributes since it's from the EQ ''' packet.send_flags packet.packet_id packet.add_ack packet.reliable ''' for block_name in message['body']: # block_data keys off of block.name, which here is the body attribute block_data = MsgBlockData(block_name) message_data.add_block(block_data) for items in message['body'][block_name]: for variable in items: var_data = MsgVariableData(variable, items[variable], -1) block_data.add_variable(var_data) packet.message_data = message_data packets.append(packet) return packets class EventQueueHandler(object): """ general class handling individual packets """ def __init__(self, settings = None): """ i do nothing """ # allow the settings to be passed in # otherwise, grab the defaults if settings != None: self.settings = settings else: from pyogp.lib.base.settings import Settings self.settings = Settings() self.handlers = {} def _register(self, name): if self.settings.LOG_VERBOSE: log(DEBUG, 'Creating a monitor for %s' % (name)) return self.handlers.setdefault(name, EventQueueReceivedNotifier(name, self.settings)) def is_packet_handled(self, name): """ if the data is being monitored, return True, otherwise, return False this can allow us to skip parsing inbound packets if no one is watching a particular one """ try: handler = self.handlers[name] return True except KeyError: return False def _handle(self, data): """ essentially a case statement to pass packets to event notifiers in the form of self attributes """ try: handler = self.handlers[data.name] # Handle the packet if we have subscribers # Conveniently, this will also enable verbose packet logging if len(handler) > 0: if self.settings.LOG_VERBOSE: log(DEBUG, 'Handling event queue data : %s' % (data.name)) handler(data) except KeyError: #log(INFO, "Received an unhandled packet: %s" % (packet.name)) pass class EventQueueReceivedNotifier(object): """ received TestMessage packet """ def __init__(self, name, settings): self.event = Event() self.name = name self.settings = settings def subscribe(self, *args, **kwdargs): self.event.subscribe(*args, **kwdargs) def received(self, data): self.event(data) def unsubscribe(self, *args, **kwdargs): self.event.unsubscribe(*args, **kwdargs) if self.settings.LOG_VERBOSE: log(DEBUG, "Removed the monitor for %s by %s" % (args, kwdargs)) def __len__(self): return len(self.event) __call__ = received