Files
Hippolyzer/pyogp/lib/base/region.py

565 lines
22 KiB
Python

"""
@file regiondomain.py
@date 2008-09-16
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$
"""
# std lib
from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG
import re
from urllib import quote
from urlparse import urlparse, urljoin
import time
import uuid
import os
# related
from indra.base import llsd
from eventlet import api, coros
# pyogp
from pyogp.lib.base.caps import Capability, SeedCapability
from pyogp.lib.base.network.stdlib_client import StdLibClient, HTTPError
import pyogp.lib.base.exc
from pyogp.lib.base.settings import Settings
from pyogp.lib.base.utilities.helpers import Helpers
from pyogp.lib.base.event_queue import EventQueueClient, EventQueueHandler
from pyogp.lib.base.objects import Objects
# messaging
from pyogp.lib.base.message.udpdispatcher import UDPDispatcher
from pyogp.lib.base.message.circuit import Host
from pyogp.lib.base.message.packets import *
from pyogp.lib.base.message.packethandler import PacketHandler
# initialize logging
logger = getLogger('pyogp.lib.base.region')
log = logger.log
class Region(object):
""" a region container
The Region class is a container for region specific data.
It is also a nice place for convenience code.
Example, of initializing a region class:
Initialize the login class
>>> region = Region(256, 256, 'https://somesim.cap/uuid', 'EnableSimulator,TeleportFinish,CrossedRegion', '127.0.0.1', 13000, 650000000, {'agent_id':'00000000-0000-0000-0000-000000000000', 'session_id':'00000000-0000-0000-0000-000000000000', 'secure_session_id:'00000000-0000-0000-0000-000000000000'})
Start the udp and event queue connections to the region
>>> region.connect()
# ToDo: finish this
Sample implementations: examples/sample_region_connect.py
Tests: tests/region.txt, tests/test_region.py
"""
def __init__(self, global_x = 0, global_y = 0, seed_capability_url = None, udp_blacklist = None, sim_ip = None, sim_port = None, circuit_code = None, agent = None, settings = None, packet_handler = None, event_queue_handler = None):
""" initialize a region """
# 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:
self.packet_handler = PacketHandler()
# 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)
# initialize the init params
self.global_x = global_x
self.global_y = global_y
self.grid_x = self.global_x/256
self.grid_y = self.global_y/256
self.seed_capability_url = seed_capability_url
self.udp_blacklist = udp_blacklist
self.sim_ip = sim_ip
self.sim_port = sim_port
self.circuit_code = circuit_code
self.agent = agent # an agent object
# UDP connection information
if (self.sim_ip != None) and (self.sim_port != None):
self.messenger = UDPDispatcher(settings = self.settings, packet_handler = self.packet_handler)
self.host = Host((self.sim_ip,self.sim_port))
else:
self.host = None
# other attributes
self.RegionHandle = None # from AgentMovementComplete
self.SimName = None
self.seed_capability = None
self.capabilities = {}
self.event_queue = None
self.connected = False
self.helpers = Helpers()
self._isUDPRunning = False
self._isEventQueueRunning = False
# data storage containers
self.packet_queue = []
if self.settings.ENABLE_OBJECT_TRACKING == True:
self.objects = Objects(agent = self.agent, region = self, settings = self.settings, packet_handler = self.packet_handler)
else:
self.objects = None
# required packet handlers
onPacketAck_received = self.packet_handler._register('PacketAck')
onPacketAck_received.subscribe(self.helpers.null_packet_handler, self)
if self.settings.MULTIPLE_SIM_CONNECTIONS:
onEnableSimulator_received = self.packet_handler._register('EnableSimulator')
onEnableSimulator_received.subscribe(onEnableSimulator, self)
# data we need
self.region_caps_list = ['ChatSessionRequest',
'CopyInventoryFromNotecard',
'DispatchRegionInfo',
'EstateChangeInfo',
'EventQueueGet',
'FetchInventory',
'WebFetchInventoryDescendents',
'FetchLib',
'FetchLibDescendents',
'GroupProposalBallot',
'HomeLocation',
'MapLayer',
'MapLayerGod',
'NewFileAgentInventory',
'ParcelPropertiesUpdate',
'ParcelVoiceInfoRequest',
'ProvisionVoiceAccountRequest',
'RemoteParcelRequest',
'RequestTextureDownload',
'SearchStatRequest',
'SearchStatTracking',
'SendPostcard',
'SendUserReport',
'SendUserReportWithScreenshot',
'ServerReleaseNotes',
'StartGroupProposal',
'UpdateAgentLanguage',
'UpdateGestureAgentInventory',
'UpdateNotecardAgentInventory',
'UpdateScriptAgent',
'UpdateGestureTaskInventory',
'UpdateNotecardTaskInventory',
'UpdateScriptTask',
'ViewerStartAuction',
'UntrustedSimulatorMessage',
'ViewerStats'
]
#set up some callbacks
if self.settings.HANDLE_PACKETS:
pass
if self.settings.LOG_VERBOSE: log(DEBUG, 'initializing region domain: %s' %self)
def enable_child_simulator(self, IP, Port, Handle):
log(INFO, "Would enable a simulator at %s:%s with a handle of %s" % (IP, Port, Handle))
def send_message_next(self, packet, reliable = False):
""" inserts this packet at the fron of the queue """
# if the packet data type != UDPPacket, serialize it
if str(type(packet)) != '<class \'pyogp.lib.base.message.packet.UDPPacket\'>':
packet = packet()
self.packet_queue.insert(0, (packet, reliable))
def enqueue_message(self, packet, reliable = False):
""" queues packets for the messaging system to send """
# if the packet data type != UDPPacket, serialize it
if str(type(packet)) != '<class \'pyogp.lib.base.message.message.Message\'>':
packet = packet()
self.packet_queue.append((packet, reliable))
def send_message(self, packet, reliable = False):
""" send a packet to the host """
# if the packet data type != UDPPacket, serialize it
if str(type(packet)) != '<class \'pyogp.lib.base.message.message.Message\'>':
packet = packet()
if self.host == None or self.messenger == None:
raise RegionMessageError(self)
else:
if reliable == False:
self.messenger.send_message(packet, self.host)
else:
self.messenger.send_reliable(packet, self.host, 0)
def send_reliable(self, packet):
""" send a reliable packet to the host """
# if the packet data type != UDPPacket, serialize it
if str(type(packet)) != '<class \'pyogp.lib.base.message.message.Message\'>':
packet = packet()
if self.host == None or self.messenger == None:
raise RegionMessageError(self)
else:
self.messenger.send_reliable(packet, self.host, 0)
def _set_seed_capability(self, url = None):
if url != None:
self.seed_capability_url = url
self.seed_cap = RegionSeedCapability('seed_cap', self.seed_capability_url, settings = self.settings)
if self.settings.ENABLE_CAPS_LOGGING: log(DEBUG, 'setting region domain seed cap: %s' % (self.seed_capability_url))
def _get_region_public_seed(self, custom_headers={'Accept' : 'application/llsd+xml'}):
""" call this capability, return the parsed result """
if self.settings.ENABLE_CAPS_LOGGING: log(DEBUG, 'Getting region public_seed %s' %(self.region_uri))
try:
restclient = StdLibClient()
response = restclient.GET(self.region_uri, custom_headers)
except HTTPError, e:
if e.code==404:
raise exc.ResourceNotFound(self.region_uri)
else:
raise exc.ResourceError(self.region_uri, e.code, e.msg, e.fp.read(), method="GET")
data = llsd.parse(response.body)
if self.settings.ENABLE_CAPS_LOGGING: log(DEBUG, 'Get of cap %s response is: %s' % (self.region_uri, data))
return data
def _get_region_capabilities(self):
""" queries the region seed cap for capabilities """
if (self.seed_cap == None):
raise exc.RegionSeedCapNotAvailable("querying for agent capabilities")
return
else:
if self.settings.ENABLE_CAPS_LOGGING: log(INFO, 'Getting caps from region seed cap %s' % (self.seed_cap))
# use self.region_caps.keys() to pass a list to be parsed into LLSD
self.capabilities = self.seed_cap.get(self.region_caps_list, self.settings)
def connect(self):
""" connect to the udp circuit code and event queue"""
# set up the seed capability
self._set_seed_capability()
# grab the agent's capabilities from the sim
self._get_region_capabilities()
# send the first few packets necessary to establish presence
self._init_agent_in_region()
self.last_ping = 0
# spawn an eventlet api instance that runs the UDP connection
log(DEBUG, 'Spawning region UDP connection')
api.spawn(self._processUDP)
# spawn an eventlet api instance that runs the event queue connection
log(DEBUG, 'Spawning region event queue connection')
self._startEventQueue()
log(DEBUG, "Spawned region data connections")
def logout(self):
""" send a logout packet """
log(INFO, "Disconnecting from region %s" % (self.SimName))
try:
# this should move to a handled method
packet = LogoutRequestPacket()
packet.AgentData['AgentID'] = uuid.UUID(self.agent.session_id)
packet.AgentData['SessionID'] = uuid.UUID(self.agent.session_id)
self.send_message(packet())
self._isUDPRunning = False
self._stopEventQueue()
return True
except:
return False
def _init_agent_in_region(self):
""" send a few packets to set things up """
# send the UseCircuitCode packet
self.sendUseCircuitCode()
# wait a sec, then send the rest
time.sleep(1)
# send the CompleteAgentMovement packet
self.sendCompleteAgentMovement()
# send a UUIDNameRequest packet
#self.sendUUIDNameRequest()
# send an AgentUpdate packet to complete the loop
self.sendAgentUpdate()
def sendUseCircuitCode(self):
""" initializing on a simulator requires announcing the circuit code an agent will use """
packet = UseCircuitCodePacket()
packet.CircuitCode['Code'] = self.circuit_code
packet.CircuitCode['SessionID'] = uuid.UUID(self.agent.session_id)
packet.CircuitCode['ID'] = uuid.UUID(self.agent.agent_id)
self.send_reliable(packet())
def sendCompleteAgentMovement(self):
""" initializing on a simulator requires sending CompleteAgentMovement, also required on teleport """
packet = CompleteAgentMovementPacket()
packet.AgentData['AgentID'] = uuid.UUID(self.agent.agent_id)
packet.AgentData['SessionID'] = uuid.UUID(self.agent.session_id)
packet.AgentData['CircuitCode'] = self.circuit_code
self.send_reliable(packet())
def sendUUIDNameRequest(self, agent_ids = []):
""" sends a packet requesting the name corresponding to a UUID """
packet = UUIDNameRequestPacket()
if agent_ids == []:
UUIDNameBlock = {}
UUIDNameBlock['ID'] = uuid.UUID(self.agent.agent_id)
packet.UUIDNameBlockBlocks.append(UUIDNameBlock)
else:
for agent_id in agent_ids:
UUIDNameBlock = {}
UUIDNameBlock['ID'] = uuid.UUID(uuid)
packet.UUIDNameBlockBlocks.append(UUIDNameBlock)
self.send_message(packet())
def sendAgentUpdate(self, BodyRotation = (0.0,0.0,0.0,1.0), HeadRotation = (0.0,0.0,0.0,1.0), State = 0x00, CameraCenter = (0.0,0.0,0.0), CameraAtAxis = (0.0,0.0,0.0), CameraLeftAxis = (0.0,0.0,0.0), CameraUpAxis = (0.0,0.0,0.0), Far = 0, ControlFlags = 0x00, Flags = 0x00):
""" sends an AgentUpdate packet """
packet = AgentUpdatePacket()
packet.AgentData['AgentID'] = uuid.UUID(str(self.agent.agent_id))
packet.AgentData['SessionID'] = uuid.UUID(str(self.agent.session_id))
# configurable data points
packet.AgentData['BodyRotation'] = BodyRotation
packet.AgentData['HeadRotation'] = HeadRotation
packet.AgentData['State'] = State
packet.AgentData['CameraCenter'] = CameraCenter
packet.AgentData['CameraAtAxis'] = CameraAtAxis
packet.AgentData['CameraLeftAxis'] = CameraLeftAxis
packet.AgentData['CameraUpAxis'] = CameraUpAxis
packet.AgentData['Far'] = Far
packet.AgentData['ControlFlags'] = ControlFlags
packet.AgentData['Flags'] = Flags
self.send_message(packet())
def sendRegionHandshakeReply(self):
""" sends a RegionHandshake packet """
packet = RegionHandshakeReplyPacket()
packet.AgentData['SessionID'] = uuid.UUID(self.agent.session_id) # MVT_LLUUID
packet.AgentData['AgentID'] = uuid.UUID(self.agent.agent_id)
packet.RegionInfo['Flags'] = 0
self.send_reliable(packet())
def sendCompletePingCheck(self):
""" sends a CompletePingCheck packet """
packet = CompletePingCheckPacket()
packet.PingID['PingID'] = self.last_ping
self.send_message(packet())
# we need to increment the last ping id
self.last_ping += 1
def _processUDP(self):
""" check for packets and handle certain cases """
self._isUDPRunning = True
# the RegionHandshake packet requires a response
onRegionHandshake_received = self.packet_handler._register('RegionHandshake')
onRegionHandshake_received.subscribe(onRegionHandshake, self)
# the StartPingCheck packet requires a response
onStartPingCheck_received = self.packet_handler._register('StartPingCheck')
onStartPingCheck_received.subscribe(onStartPingCheck, self)
while self._isUDPRunning:
# free up resources for other stuff to happen
api.sleep(0)
# check for new messages
msg_buf, msg_size = self.messenger.udp_client.receive_packet(self.messenger.socket)
self.messenger.receive_check(self.messenger.udp_client.get_sender(),
msg_buf, msg_size)
if self.messenger.has_unacked():
self.messenger.process_acks()
# pull the camera back a bit, 20m
# we are currently facing east, so pull back on the x axis
CameraCenter = (self.agent.Position.X - 20.0, self.agent.Position.Y, self.agent.Position.Z)
self.sendAgentUpdate(CameraCenter = CameraCenter, CameraAtAxis = self.settings.DEFAULT_CAMERA_AT_AXIS, CameraLeftAxis = self.settings.DEFAULT_CAMERA_AT_AXIS, CameraUpAxis = self.settings.DEFAULT_CAMERA_UP_AXIS, Far = self.settings.DEFAULT_CAMERA_DRAW_DISTANCE)
# send pending messages in the queue
for (packet, reliable) in self.packet_queue:
self.send_message(packet, reliable)
self.packet_queue.remove((packet, reliable))
log(DEBUG, "Stopped the UDP connection for %s" % (self.SimName))
def _startEventQueue(self):
""" polls the event queue capability and parses the results """
self.event_queue = EventQueueClient(self.capabilities['EventQueueGet'], settings = self.settings, packet_handler = self.packet_handler, region = self, event_queue_handler = self.event_queue_handler)
api.spawn(self.event_queue.start)
self._isEventQueueRunning = True
def _stopEventQueue(self):
""" shuts down the running event queue """
if self._isEventQueueRunning == True and self.event_queue._running == True:
self.event_queue.stop = True
def onRegionHandshake(packet, region):
""" handles the response to receiving a RegionHandshake packet """
# send the reply
region.sendRegionHandshakeReply()
# propagate the incoming data
region.SimName = packet.message_data.blocks['RegionInfo'][0].get_variable('SimName')
region.SimAccess = packet.message_data.blocks['RegionInfo'][0].get_variable('SimAccess')
region.SimOwner = packet.message_data.blocks['RegionInfo'][0].get_variable('SimOwner')
region.IsEstateManager = packet.message_data.blocks['RegionInfo'][0].get_variable('IsEstateManager')
region.WaterHeight = packet.message_data.blocks['RegionInfo'][0].get_variable('WaterHeight')
region.BillableFactor = packet.message_data.blocks['RegionInfo'][0].get_variable('BillableFactor')
region.TerrainBase0 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainBase0')
region.TerrainBase1 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainBase1')
region.TerrainBase2 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainBase2')
region.TerrainStartHeight00 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainStartHeight00')
region.TerrainStartHeight01 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainStartHeight01')
region.TerrainStartHeight10 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainStartHeight10')
region.TerrainStartHeight11 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainStartHeight11')
region.TerrainHeightRange00 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainHeightRange00')
region.TerrainHeightRange01 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainHeightRange01')
region.TerrainHeightRange10 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainHeightRange10')
region.TerrainHeightRange11 = packet.message_data.blocks['RegionInfo'][0].get_variable('TerrainHeightRange11')
region.CPUClassID = packet.message_data.blocks['RegionInfo3'][0].get_variable('CPUClassID')
region.CPURatio = packet.message_data.blocks['RegionInfo3'][0].get_variable('CPURatio')
region.ColoName = packet.message_data.blocks['RegionInfo3'][0].get_variable('ColoName')
region.ProductSKU = packet.message_data.blocks['RegionInfo3'][0].get_variable('ProductSKU')
region.ProductName = packet.message_data.blocks['RegionInfo3'][0].get_variable('ProductName')
region.RegionID = packet.message_data.blocks['RegionInfo2'][0].get_variable('RegionID')
# we are connected
region.connected = True
log(INFO, "Rezzed agent \'%s %s\' in region %s" % (region.agent.firstname, region.agent.lastname, region.SimName))
def onStartPingCheck(packet, region):
""" sends the CompletePingCheck packet """
region.sendCompletePingCheck()
def onEnableSimulator(packet, region):
""" handler for the EnableSimulator packet sent over the event queue """
IP = [ord(x) for x in packet.message_data.blocks['SimulatorInfo'][0].get_variable('IP').data]
IP = '.'.join([str(x) for x in IP])
Port = packet.message_data.blocks['SimulatorInfo'][0].get_variable('Port').data
# not sure what this is, but pass it up
Handle = [ord(x) for x in packet.message_data.blocks['SimulatorInfo'][0].get_variable('Handle').data]
region.enable_child_simulator(IP, Port, Handle)
class RegionSeedCapability(Capability):
""" a seed capability which is able to retrieve other capabilities """
def get(self, names=[], settings = None):
"""if this is a seed cap we can retrieve other caps here"""
# 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()
#log(INFO, 'requesting from the region domain the following caps: %s' % (names))
payload = names
parsed_result = self.POST(payload) #['caps']
if self.settings.ENABLE_CAPS_LOGGING: log(INFO, 'Request for caps returned: %s' % (parsed_result.keys()))
caps = {}
for name in names:
# TODO: some caps might be seed caps, how do we know?
if parsed_result.has_key(name):
caps[name]=Capability(name, parsed_result[name], settings = self.settings)
else:
if self.settings.ENABLE_CAPS_LOGGING: log(DEBUG, 'Requested capability \'%s\' is not available' % (name))
#log(INFO, 'got cap: %s' % (name))
return caps
def __repr__(self):
return "<RegionSeedCapability for %s>" % (self.public_url)