class ClientAccountManager(ClientOperationManager): notify = notify.new_category('ClientAccountManager') def __init__(self, *args, **kwargs): ClientOperationManager.__init__(self, *args, **kwargs) self._dbm = semidbm.open( config.GetString('clientagent-dbm-filename', 'databases/database.dbm'), config.GetString('clientagent-dbm-mode', 'c')) @property def dbm(self): return self._dbm def handle_operation(self, operationFSM, client, callback, *args, **kwargs): operation = self.run_operation(operationFSM, client, callback, *args, **kwargs) if not operation: self.notify.warning('Failed to handle operation: %r!' % operationFSM) return operation.request('Start')
class MessageDirector(io.NetworkListener, component.Component): notify = notify.new_category('MessageDirector') def __init__(self): address = config.GetString('messagedirector-address', '0.0.0.0') port = config.GetInt('messagedirector-port', 7100) io.NetworkListener.__init__(self, address, port, Participant) self._interface = ParticipantInterface(self) self._message_interface = MessageInterface(self) @property def interface(self): return self._interface @property def message_interface(self): return self._message_interface def setup(self): io.NetworkListener.setup(self) self._message_interface.setup() def shutdown(self): self._message_interface.shutdown() io.NetworkListener.shutdown(self)
class RetrieveAvatarsFSM(ClientOperation): notify = notify.new_category('RetrieveAvatarsFSM') def __init__(self, manager, client, callback, account_id): ClientOperation.__init__(self, manager, client, callback) self._account_id = account_id self._pending_avatars = [] self._avatar_fields = {} def enterStart(self): self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._account_id, self.__account_loaded, self.manager.network.dc_loader.dclasses_by_name['Account']) def exitStart(self): pass def __account_loaded(self, dclass, fields): if not dclass: self.cleanup(False) return avatar_list = fields['ACCOUNT_AV_SET'][0] for avatar_id in avatar_list: if not avatar_id: continue self._pending_avatars.append(avatar_id) def response(dclass, fields, avatar_id=avatar_id): self._avatar_fields[avatar_id] = fields self._pending_avatars.remove(avatar_id) if not self._pending_avatars: self.request('SetAvatars') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, avatar_id, response, self.manager.network.dc_loader. dclasses_by_name['DistributedToon']) if not self._pending_avatars: self.request('SetAvatars') def enterSetAvatars(self): avatar_list = [] for avatar_id, fields in self._avatar_fields.items(): avatar_data = ClientAvatarData(avatar_id, [fields['setName'][0], '', '', ''], fields['setDNAString'][0], fields['setPosIndex'][0], 0) avatar_list.append(avatar_data) # we're all done. self.cleanup(True, avatar_list) def exitSetAvatars(self): pass
class SetNameFSM(ClientOperation): notify = notify.new_category('SetNameFSM') def __init__(self, manager, client, callback, avatar_id, wish_name): self.notify.debug("SetNameFSM.__init__(%s, %s, %s, %s, %s)" % (str(manager), str(client), str(callback), str(avatar_id), str(wish_name))) ClientOperation.__init__(self, manager, client, callback) self._avatar_id = avatar_id self._wish_name = wish_name self._callback = callback self._dc_class = None self._fields = {} def enterStart(self): self.notify.debug("SetNameFSM.enterQuery()") def response(dclass, fields): self.notify.debug("SetNameFSM.enterQuery.response(%s, %s)" % (str(dclass), str(fields))) if not dclass: self.cleanup(False) return self._dc_class = dclass self._fields = fields self.request('SetName') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, response, self.manager.network.dc_loader.dclasses_by_name['DistributedToon']) def exitStart(self): self.notify.debug("SetNameFSM.exitQuery()") def enterSetName(self): self.notify.debug("SetNameFSM.enterSetName()") # TODO: Parse a check the wish-name for bad names and etc. new_fields = {'setName': (self._wish_name, )} #self.notify.warning("New fields are \n%s" % (str(self._fields))) self.manager.network.database_interface.update_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, self.manager.network.dc_loader.dclasses_by_name['DistributedToon'], new_fields) # We're all done self.cleanup(True, self._avatar_id, self._wish_name) def exitSetName(self): self.notify.debug("SetNameFSM.exitSetName()")
class ClientOperation(FSM): notify = notify.new_category('ClientOperation') def __init__(self, manager, client, callback): self.notify.debug('Starting FSM operation %s!' % (self.__class__.__name__)) FSM.__init__(self, self.__class__.__name__) self._manager = manager self._client = client self._callback = callback @property def manager(self): return self._manager @property def client(self): return self._client @property def callback(self): return self._callback @callback.setter def callback(self, callback): self._callback = callback def enterOff(self): pass def exitOff(self): pass def defaultFilter(self, request, *args): return FSM.defaultFilter(self, request, *args) def cleanup(self, success, *args, **kwargs): self.notify.debug('Stopping operation %s for %s.' % (self.__class__.__name__, self.client)) self.ignoreAll() self.manager.stop_operation(self.client) self.demand('Off') # only initiate callback if the cleanup was successful... if self._callback and success: self._callback(*args, **kwargs) elif not success: self.notify.debug('Cleanup operation %s was unsuccessful for %s.' % (self.__class__.__name__, self.client))
class NetworkManager(object): notify = notify.new_category('NetworkManager') def get_unique_name(self, name): return '%s-%s-%s' % (self.__class__.__name__, name, id(self)) def get_puppet_connection_channel(self, doId): return doId + (1001 << 32) def get_account_connection_channel(self, doId): return doId + (1003 << 32) def get_account_id_from_channel_code(self, channel): return channel >> 32 def get_avatar_id_from_connection_channel(self, channel): return channel & 0xffffffff
class Participant(io.NetworkHandler): notify = notify.new_category('Participant') def register_for_channel(self, channel): io.NetworkChannelManager.register_for_channel(self, channel) self.network.interface.add_participant(channel, self) def unregister_for_channel(self, channel): self.network.interface.remove_participant(channel) io.NetworkChannelManager.unregister_for_channel(self, channel) def handle_datagram(self, di): channels = di.get_uint8() channel = di.get_uint64() if channels == 1 and channel == types.CONTROL_MESSAGE: message_type = di.get_uint16() sender = di.get_uint64() if message_type == types.CONTROL_SET_CHANNEL: self.register_for_channel(sender) elif message_type == types.CONTROL_REMOVE_CHANNEL: self.unregister_for_channel(sender) elif message_type == types.CONTROL_SET_CON_NAME: pass elif message_type == types.CONTROL_SET_CON_URL: pass elif message_type == types.CONTROL_ADD_RANGE: pass elif message_type == types.CONTROL_REMOVE_RANGE: pass elif message_type == types.CONTROL_ADD_POST_REMOVE: self.network.message_interface.append_post_handle( sender, io.NetworkDatagram(Datagram(di.get_remaining_bytes()))) elif message_type == types.CONTROL_CLEAR_POST_REMOVE: self.network.message_interface.clear_post_handles(sender) else: self.notify.warning('Failed to handle unknown datagram with ' 'message type: %d!' % message_type) else: self.network.message_interface.append_handle( channel, di.get_uint64(), di.get_uint16(), io.NetworkDatagram(Datagram(di.get_remaining_bytes())))
class DatabaseOperationFSM(FSM): notify = notify.new_category('DatabaseOperationFSM') def __init__(self, network, sender, callback=None): FSM.__init__(self, self.__class__.__name__) self._network = network self._sender = sender self._callback = callback @property def network(self): return self._network @property def sender(self): return self._sender @property def callback(self): return self._callback def enterOff(self): pass def exitOff(self): pass def enterStart(self): pass def exitStart(self): pass def cleanup(self, success, *args, **kwargs): self.ignoreAll() self.demand('Off') if self.callback: self.callback(success, self._sender, *args, **kwargs)
class DatabaseOperationManager(object): notify = notify.new_category('DatabaseOperationManager') def __init__(self): self._operations = collections.deque() self._flush_timeout = config.GetFloat('database-flush-timeout', 0.001) self.__flush_task = None @property def operations(self): return self._operations def add_operation(self, fsm_class, *args, **kwargs): operation = fsm_class(*args, **kwargs) self._operations.append(operation) def setup(self): self.__flush_task = task_mgr.doMethodLater(self._flush_timeout, self.__flush, 'database-flush') def __flush(self, task): """ Gets an database operation from the queue and processes it... """ while len(self._operations) > 0: operation = self._operations.popleft() operation.request('Start') return task.again def shutdown(self): if self.__flush_task: task_mgr.remove(self.__flush_task) self.__flush_task = None self._operations = None
class ComponentManager(object): notify = notify.new_category('ComponentManager') def __init__(self): self._components = [] @property def components(self): return self._components def has_component(self, component): assert (isinstance(component, Component)) return component in self._components def add_component(self, component): assert (isinstance(component, Component)) if component in self._components: return self.notify.info('Starting component: %s...' % component.__class__.__name__) self._components.append(component) component.setup() def remove_component(self, component): assert (isinstance(component, Component)) if component not in self._components: return self.notify.info('Shutting down component: %s...' % component.__class__.__name__) self._components.remove(component) component.shutdown() def shutdown(self): assert (len(self._components) > 0) for component in list(self._components): self.remove_component(component)
class ParticipantInterface(object): notify = notify.new_category('ParticipantInterface') def __init__(self, network): self._network = network self._participants = {} @property def participants(self): return self._participants def has_participant(self, channel): return channel in self._participants def add_participant(self, channel, participant): if self.has_participant(channel): self.notify.warning('Failed to add participant with channel: %d, ' 'participant already exists!' % channel) return self._participants[channel] = participant def remove_participant(self, channel): if not self.has_participant(channel): self.notify.warning( 'Failed to remove participant with channel: %d, ' 'participant does not exist!' % channel) return self._network.message_interface.flush_post_handles(channel) del self._participants[channel] def get_participant(self, channel): return self._participants.get(channel)
class LoadAvatarFSM(ClientOperation): notify = notify.new_category('LoadAvatarFSM') def __init__(self, manager, client, callback, account_id, avatar_id): ClientOperation.__init__(self, manager, client, callback) self._account_id = account_id self._avatar_id = avatar_id self._dc_class = None self._fields = {} def enterStart(self): if self._avatar_id == 0: self.cleanup(False) return def response(dclass, fields): if not dclass: self.cleanup(False) return self._dc_class = dclass self._fields = fields self.request('Activate') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, response, self.manager.network.dc_loader.dclasses_by_name['DistributedToon']) def exitStart(self): pass def _handle_activate_avatar(self, task): # setup a post remove message that will delete the # client's toon object when they disconnect... post_remove = io.NetworkDatagram() post_remove.add_header(self._avatar_id, self.client.channel, types.STATESERVER_OBJECT_DELETE_RAM) post_remove.add_uint32(self._avatar_id) datagram = io.NetworkDatagram() datagram.add_control_header(self.client.channel, types.CONTROL_ADD_POST_REMOVE) datagram.append_data(post_remove.get_message()) self.manager.network.handle_send_connection_datagram(datagram) # grant ownership over the distributed object... datagram = io.NetworkDatagram() datagram.add_header(self._avatar_id, self.client.channel, types.STATESERVER_OBJECT_SET_OWNER) datagram.add_uint64(self.client.channel) self.manager.network.handle_send_connection_datagram(datagram) # we're all done. self.cleanup(True, self._avatar_id) return task.done def enterActivate(self): # add them to the avatar channel channel = self.client.get_puppet_connection_channel(self._avatar_id) self.client.register_for_channel(channel) # set their sender channel to represent their account affiliation channel = self._account_id << 32 | self._avatar_id self.client.handle_set_channel_id(channel) datagram = io.NetworkDatagram() datagram.add_header( types.STATESERVER_CHANNEL, channel, types.STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER) datagram.add_uint32(self._avatar_id) datagram.add_uint32(0) datagram.add_uint32(0) datagram.add_uint16(self._dc_class.get_number()) sorted_fields = {} for field_name, field_args in self._fields.items(): field = self._dc_class.get_field_by_name(field_name) if not field: self.notify.warning('Failed to pack fields for object %d, ' 'unknown field: %s!' % (self._avatar_id, field_name)) return sorted_fields[field.get_number()] = field_args sorted_fields = collections.OrderedDict(sorted(sorted_fields.items())) field_packer = DCPacker() for field_index, field_args in sorted_fields.items(): field = self._dc_class.get_field_by_index(field_index) if not field: self.notify.error( 'Failed to pack required field: %d for object %d, ' 'unknown field!' % (field_index, self._avatar_id)) field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.append_data(field_packer.get_string()) # add a few fields so we ensure we actually have some data to append # with the generate call, as we must always have some field data in this message: other_fields = { 'setCommonChatFlags': (self._fields.get('setCommonChatFlags', 0), ), 'setTrophyScore': (self._fields.get('setTrophyScore', 0), ), } field_packer = DCPacker() for field_name, field_args in other_fields.items(): field = self._dc_class.get_field_by_name(field_name) if not field: self.notify.error( 'Failed to pack other field: %s for object %d, ' 'unknown field!' % (field_name, self._avatar_id)) field_packer.raw_pack_uint16(field.get_number()) field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.add_uint16(len(other_fields)) datagram.append_data(field_packer.get_string()) self.manager.network.handle_send_connection_datagram(datagram) taskMgr.doMethodLater(0.2, self._handle_activate_avatar, 'activate-avatar-%d-task' % self._avatar_id) def exitActivate(self): pass
class Client(io.NetworkHandler): notify = notify.new_category('Client') def __init__(self, *args, **kwargs): io.NetworkHandler.__init__(self, *args, **kwargs) self.channel = self.network.channel_allocator.allocate() self._authenticated = False self._interest_manager = InterestManager() self._location_deferred_callback = None self.__interest_timeout_task = None self._generate_deferred_callback = None self._seen_objects = {} self._owned_objects = [] self._pending_objects = [] self._dna_stores = {} @property def authenticated(self): return self._authenticated @authenticated.setter def authenticated(self, authenticated): self._authenticated = authenticated def has_seen_object(self, do_id): for zone_id, seen_objects in list(self._seen_objects.items()): if do_id in seen_objects: return True return False def remove_seen_object(self, do_id): for zone_id, seen_objects in list(self._seen_objects.items()): if do_id in seen_objects: seen_objects.remove(do_id) if len(seen_objects) == 0: del self._seen_objects[zone_id] def get_seen_object_zone(self, do_id): for zone_id, seen_objects in list(self._seen_objects.items()): if do_id in seen_objects: return zone_id return -1 def handle_send_disconnect(self, code, reason): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GO_GET_LOST) datagram.add_uint16(code) datagram.add_string(reason) self.handle_send_datagram(datagram) self.handle_disconnect() def handle_datagram(self, di): try: message_type = di.get_uint16() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return if message_type == types.CLIENT_HEARTBEAT: pass elif message_type == types.CLIENT_LOGIN_2: self.handle_login(di) elif message_type == types.CLIENT_DISCONNECT: self.handle_disconnect() else: if not self._authenticated: self.handle_send_disconnect( types.CLIENT_DISCONNECT_ANONYMOUS_VIOLATION, 'Cannot send datagram with message type: %d, channel: %d not yet authenticated!' % (message_type, self.channel)) return else: self.handle_authenticated_datagram(message_type, di) def handle_authenticated_datagram(self, message_type, di): if message_type == types.CLIENT_GET_SHARD_LIST: self.handle_get_shard_list() elif message_type == types.CLIENT_GET_AVATARS: self.handle_get_avatars() elif message_type == types.CLIENT_GET_AVATAR_DETAILS: self.handle_get_avatar_details(di) elif message_type == types.CLIENT_CREATE_AVATAR: self.handle_create_avatar(di) elif message_type == types.CLIENT_SET_AVATAR: self.handle_set_avatar(di) elif message_type == types.CLIENT_SET_WISHNAME: self.handle_set_wishname(di) elif message_type == types.CLIENT_SET_NAME_PATTERN: self.handle_set_name_pattern(di) elif message_type == types.CLIENT_DELETE_AVATAR: self.handle_delete_avatar(di) elif message_type == types.CLIENT_GET_FRIEND_LIST: self.handle_get_friends_list(di) elif message_type == types.CLIENT_REMOVE_FRIEND: pass elif message_type == types.CLIENT_SET_SHARD: self.handle_set_shard(di) elif message_type == types.CLIENT_SET_ZONE: self.handle_set_zone(di) elif message_type == types.CLIENT_OBJECT_UPDATE_FIELD: self.handle_object_update_field(di) else: self.handle_send_disconnect( types.CLIENT_DISCONNECT_INVALID_MSGTYPE, 'Unknown datagram: %d from channel: %d!' % (message_type, self.channel)) return def handle_internal_datagram(self, message_type, sender, di): if message_type == types.CLIENTAGENT_DISCONNECT: self.handle_send_disconnect(di.get_uint16(), di.get_string()) elif message_type == types.CLIENTAGENT_FRIEND_ONLINE: self.handle_friend_online(di) elif message_type == types.CLIENTAGENT_FRIEND_OFFLINE: self.handle_friend_offline(di) elif message_type == types.STATESERVER_GET_SHARD_ALL_RESP: self.handle_get_shard_list_resp(di) elif message_type == types.STATESERVER_SERVER_UP: self.handle_server_up(di) elif message_type == types.STATESERVER_SERVER_DOWN: self.handle_server_down(di) elif message_type == types.STATESERVER_OBJECT_LOCATION_ACK: self.handle_object_location_ack(di) elif message_type == types.STATESERVER_OBJECT_GET_ZONES_OBJECTS_RESP: self.handle_object_get_zones_objects_resp(di) elif message_type == types.STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED: self.handle_object_enter_owner(False, di) elif message_type == types.STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER: self.handle_object_enter_owner(True, di) elif message_type == types.STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED: self.handle_object_enter_location(False, di) elif message_type == types.STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER: self.handle_object_enter_location(True, di) elif message_type == types.STATESERVER_OBJECT_CHANGING_LOCATION: self.handle_object_changing_location(di) elif message_type == types.STATESERVER_OBJECT_UPDATE_FIELD: self.handle_object_update_field_resp(di) elif message_type == types.STATESERVER_OBJECT_DELETE_RAM: self.handle_object_delete_ram(di) else: self.network.database_interface.handle_datagram(message_type, di) def handle_login(self, di): try: play_token = di.get_string() server_version = di.get_string() hash_val = di.get_uint32() token_type = di.get_int32() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return if server_version != self.network.server_version: self.handle_send_disconnect( types.CLIENT_DISCONNECT_BAD_VERSION, 'Invalid server version: %s, expected: %s!' % (server_version, self.network.server_version)) return if hash_val != self.network.server_hash_val: self.handle_send_disconnect( types.CLIENT_DISCONNECT_BAD_DCHASH, 'Got an invalid DC hash value: %d expected: %d!' % (hash_val, self.network.server_hash_val)) return if token_type != types.CLIENT_LOGIN_2_BLUE and token_type != CLIENT_LOGIN_2_PLAY_TOKEN: self.handle_send_disconnect( types.CLIENT_DISCONNECT_INVALID_PLAY_TOKEN_TYPE, 'Invalid play token type: %d!' % token_type) return self.network.account_manager.handle_operation(LoadAccountFSM, self, self.__handle_login_resp, play_token) def __handle_login_resp(self, play_token): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_LOGIN_2_RESP) datagram.add_uint8(0) datagram.add_string('All Ok') datagram.add_string(play_token) datagram.add_uint8(1) datagram.add_uint32(int(time.time())) datagram.add_uint32(int(time.clock())) datagram.add_uint8(1) datagram.add_int32(1000 * 60 * 60) self.handle_send_datagram(datagram) def handle_get_shard_list(self): datagram = io.NetworkDatagram() datagram.add_header(types.STATESERVER_CHANNEL, self.channel, types.STATESERVER_GET_SHARD_ALL) self.network.handle_send_connection_datagram(datagram) def handle_get_shard_list_resp(self, di): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_SHARD_LIST_RESP) datagram.append_data(di.get_remaining_bytes()) self.handle_send_datagram(datagram) def handle_server_up(self, di): shard_id = di.get_uint32() shard_name = di.get_string() datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_SERVER_UP) datagram.add_uint32(shard_id) datagram.add_string(shard_name) self.handle_send_datagram(datagram) def handle_server_down(self, di): shard_id = di.get_uint32() datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_SERVER_DOWN) datagram.add_uint32(shard_id) self.handle_send_datagram(datagram) def handle_get_avatars(self): account_id = self.get_account_id_from_channel_code(self.channel) self.network.account_manager.handle_operation( RetrieveAvatarsFSM, self, self.__handle_retrieve_avatars_resp, account_id) def __handle_retrieve_avatars_resp(self, avatar_data): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_AVATARS_RESP) datagram.add_uint8(0) datagram.add_uint16(len(avatar_data)) for avatar in avatar_data: datagram.add_uint32(avatar.do_id) datagram.add_string(avatar.name_list[0]) datagram.add_string(avatar.name_list[1]) datagram.add_string(avatar.name_list[2]) datagram.add_string(avatar.name_list[3]) datagram.add_string(avatar.dna) datagram.add_uint8(avatar.position) datagram.add_uint8(avatar.name_index) self.handle_send_datagram(datagram) def handle_create_avatar(self, di): try: echo_context = di.get_uint16() dna_string = di.get_string() index = di.get_uint8() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return account_id = self.get_account_id_from_channel_code(self.channel) self.network.account_manager.handle_operation( CreateAvatarFSM, self, self.__handle_create_avatar_resp, echo_context, account_id, dna_string, index) def __handle_create_avatar_resp(self, echo_context, avatar_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_CREATE_AVATAR_RESP) datagram.add_uint16(echo_context) datagram.add_uint8(0) datagram.add_uint32(avatar_id) self.handle_send_datagram(datagram) def _delete_ownerviews(self): account_id = self.get_account_id_from_channel_code(self.channel) avatar_id = self.get_avatar_id_from_connection_channel(self.channel) # close the avatar channel: channel = self.get_puppet_connection_channel(avatar_id) self.unregister_for_channel(channel) # once again open the account channel, also closing the avatar sender channel: channel = account_id << 32 self.handle_set_channel_id(channel) # clear any of the cached distributed objects: self._seen_objects = {} self._owned_objects = [] self._pending_objects = [] self._dna_stores = {} # clear our old interest zones: self._interest_manager.clear() def handle_set_avatar(self, di): try: avatar_id = di.get_uint32() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return # check to see if the client want's to delete their ownerview, # we assume the client has logged out to the avatar chooser: if avatar_id == 0: self._delete_ownerviews() return # before continuing to activate an avatar with the associated ID sent above, # check to make sure the client does not already has an actively generated avatar on the SS: if len(self._owned_objects) > 0: return account_id = self.get_account_id_from_channel_code(self.channel) self.network.account_manager.handle_operation( LoadAvatarFSM, self, self.__handle_set_avatar_resp, account_id, avatar_id) def __handle_set_avatar_resp(self, avatar_id): pass def handle_friend_online(self, di): friend_id = di.get_uint32() datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_FRIEND_ONLINE) datagram.add_uint32(friend_id) self.handle_send_datagram(datagram) def handle_friend_offline(self, di): friend_id = di.get_uint32() datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_FRIEND_OFFLINE) datagram.add_uint32(friend_id) self.handle_send_datagram(datagram) def handle_get_avatar_details(self, di): try: avatar_id = di.get_uint32() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return self.network.account_manager.handle_operation( GetAvatarDetailsFSM, self, self.handle_object_enter_owner, avatar_id) def handle_set_wishname(self, di): try: avatar_id = di.get_uint32() wish_name = di.get_string() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return self.network.account_manager.handle_operation( SetNameFSM, self, self.__handle_set_wishname_resp, avatar_id, wish_name) def __handle_set_wishname_resp(self, avatar_id, wish_name): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_SET_WISHNAME_RESP) datagram.add_uint32(avatar_id) datagram.add_uint16(0) datagram.add_string('') datagram.add_string(wish_name) datagram.add_string('') self.handle_send_datagram(datagram) def handle_set_name_pattern(self, di): try: name_indices = [] name_flags = [] avatar_id = di.get_uint32() name_indices.append(di.get_uint16()) name_flags.append(di.get_uint16()) name_indices.append(di.get_uint16()) name_flags.append(di.get_uint16()) name_indices.append(di.get_uint16()) name_flags.append(di.get_uint16()) name_indices.append(di.get_uint16()) name_flags.append(di.get_uint16()) except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return pattern = [(name_indices[0], name_flags[0]), (name_indices[1], name_flags[1]), (name_indices[2], name_flags[2]), (name_indices[3], name_flags[3])] self.network.account_manager.handle_operation( SetNamePatternFSM, self, self.__handle_set_name_pattern_resp, avatar_id, pattern) def __handle_set_name_pattern_resp(self, avatar_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_SET_NAME_PATTERN_ANSWER) datagram.add_uint32(avatar_id) datagram.add_uint8(0) self.handle_send_datagram(datagram) def handle_get_friends_list(self, di): account_id = self.get_account_id_from_channel_code(self.channel) avatar_id = self.get_avatar_id_from_connection_channel(self.channel) self.network.account_manager.handle_operation( LoadFriendsListFSM, self, self.__handle_get_friends_list_callback, account_id, avatar_id) def __handle_get_friends_list_callback(self): pass def handle_delete_avatar(self, di): try: avatar_id = di.get_uint32() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return account_id = self.get_account_id_from_channel_code(self.channel) self.network.account_manager.handle_operation( DeleteAvatarFSM, self, self.__handle_delete_avatar_resp, account_id, avatar_id) def __handle_delete_avatar_resp(self, avatar_data): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_DELETE_AVATAR_RESP) datagram.add_uint8(0) datagram.add_uint16(len(avatar_data)) for avatar in avatar_data: datagram.add_uint32(avatar.do_id) datagram.add_string(avatar.name_list[0]) datagram.add_string(avatar.name_list[1]) datagram.add_string(avatar.name_list[2]) datagram.add_string(avatar.name_list[3]) datagram.add_string(avatar.dna) datagram.add_uint8(avatar.position) datagram.add_uint8(avatar.name_index) self.handle_send_datagram(datagram) def handle_set_shard(self, di): try: shard_id = di.get_uint32() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return avatar_id = self.get_avatar_id_from_connection_channel(self.channel) self._location_deferred_callback = util.DeferredCallback( self.handle_set_shard_callback) datagram = io.NetworkDatagram() datagram.add_header(avatar_id, self.channel, types.STATESERVER_OBJECT_SET_AI) datagram.add_uint64(shard_id) self.network.handle_send_connection_datagram(datagram) def handle_set_shard_callback(self, do_id, old_parent_id, old_zone_id, new_parent_id, new_zone_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_STATE_RESP) self.handle_send_datagram(datagram) def handle_set_zone(self, di): try: zone_id = di.get_uint16() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % (self._channel)) return avatar_id = self.get_avatar_id_from_connection_channel(self.channel) self._location_deferred_callback = util.DeferredCallback( self.handle_set_zone_callback) datagram = io.NetworkDatagram() datagram.add_header(avatar_id, self.channel, types.STATESERVER_OBJECT_SET_ZONE) datagram.add_uint32(zone_id) self.network.handle_send_connection_datagram(datagram) def send_get_zones_objects(self, avatar_id, interest_zones): datagram = io.NetworkDatagram() datagram.add_header(avatar_id, self.channel, types.STATESERVER_OBJECT_GET_ZONES_OBJECTS) # pack the interest zones datagram.add_uint16(len(interest_zones)) for interest_zone in interest_zones: datagram.add_uint32(interest_zone) self.network.handle_send_connection_datagram(datagram) def get_in_street_branch(self, zone_id): if not ZoneUtil.isPlayground(zone_id): where = ZoneUtil.getWhereName(zone_id, True) return where == 'street' return False def get_vis_branch_zones(self, zone_id): branch_zone_id = ZoneUtil.getBranchZone(zone_id) dnaStore = self._dna_stores.get(branch_zone_id) if not dnaStore: dnaStore = DNAStorage() dnaFileName = genDNAFileName(branch_zone_id) loadDNAFileAI(dnaStore, dnaFileName) self._dna_stores[branch_zone_id] = dnaStore zoneVisDict = {} for i in xrange(dnaStore.getNumDNAVisGroupsAI()): groupFullName = dnaStore.getDNAVisGroupName(i) visGroup = dnaStore.getDNAVisGroupAI(i) visZoneId = int(extractGroupName(groupFullName)) visZoneId = ZoneUtil.getTrueZoneId(visZoneId, zone_id) visibles = [] for i in xrange(visGroup.getNumVisibles()): visibles.append(int(visGroup.visibles[i])) visibles.append(ZoneUtil.getBranchZone(visZoneId)) zoneVisDict[visZoneId] = visibles return zoneVisDict[zone_id] def handle_set_zone_callback(self, do_id, old_parent_id, old_zone_id, new_parent_id, new_zone_id): if self._location_deferred_callback: self._location_deferred_callback.destroy() self._location_deferred_callback = None old_zone_in_street_branch = self.get_in_street_branch(old_zone_id) new_zone_in_street_branch = self.get_in_street_branch(new_zone_id) old_vis_zones = set() if old_zone_in_street_branch: old_branch_zone_id = ZoneUtil.getBranchZone(old_zone_id) old_vis_zones.update(self.get_vis_branch_zones(old_zone_id)) for zone_id in self._interest_manager.interest_zones.difference( old_vis_zones): self._interest_manager.remove_interest_zone(zone_id) else: self._interest_manager.remove_interest_zone(old_zone_id) if old_zone_id != OTP_ZONE_ID_OLD_QUIET_ZONE: self._interest_manager.remove_interest_zone( OTP_ZONE_ID_OLD_QUIET_ZONE) new_vis_zones = set() if new_zone_in_street_branch: new_branch_zone_id = ZoneUtil.getBranchZone(old_zone_id) new_vis_zones.update(self.get_vis_branch_zones(new_zone_id)) for zone_id in new_vis_zones.difference(old_vis_zones): self._interest_manager.add_interest_zone(zone_id) else: self._interest_manager.add_interest_zone(new_zone_id) if new_zone_id != OTP_ZONE_ID_OLD_QUIET_ZONE: self._interest_manager.add_interest_zone( OTP_ZONE_ID_OLD_QUIET_ZONE) # clear the dna store for this branch zones since # they have left the street branch if (old_zone_in_street_branch and not new_zone_in_street_branch) or\ (old_zone_in_street_branch and new_zone_in_street_branch and old_branch_zone_id != new_branch_zone_id): # remove the branch dna zone store branch_zone_id = ZoneUtil.getBranchZone(old_zone_id) del self._dna_stores[branch_zone_id] # remove the old street zones old_vis_zones = self.get_vis_branch_zones(old_zone_id) for zone_id in old_vis_zones: self._interest_manager.remove_interest_zone(zone_id) # destroy the objects we no longer have interest in for zone_id in dict(self._seen_objects): if self._interest_manager.has_interest_zone(zone_id): continue seen_objects = list(self._seen_objects[zone_id]) for do_id in seen_objects: if do_id in self._owned_objects: continue self.remove_seen_object(do_id) self.send_client_object_delete_resp(do_id) # only run a deferred callback if we moved to or from a non street zone if not old_zone_in_street_branch or not new_zone_in_street_branch: self._generate_deferred_callback = util.DeferredCallback( self.handle_set_zone_complete_callback, old_parent_id, old_zone_id, new_parent_id, new_zone_id) # request all of the objects in the zones we have interest in avatar_id = self.get_avatar_id_from_connection_channel(self.channel) interest_zones = list(self._interest_manager.interest_zones) self.send_get_zones_objects(avatar_id, interest_zones) def handle_object_location_ack(self, di): do_id = di.get_uint32() old_parent_id = di.get_uint32() old_zone_id = di.get_uint32() new_parent_id = di.get_uint32() new_zone_id = di.get_uint32() if self._location_deferred_callback: self._location_deferred_callback.callback(do_id, old_parent_id, old_zone_id, new_parent_id, new_zone_id) def handle_object_get_zones_objects_resp(self, di): do_id = di.get_uint64() num_objects = di.get_uint16() for _ in xrange(num_objects): do_id = di.get_uint64() if self.has_seen_object(do_id): continue if do_id in self._owned_objects: continue self._pending_objects.append(do_id) if self._generate_deferred_callback: self._generate_deferred_callback.callback(False) if not num_objects: self._handle_interest_done() return if self.__interest_timeout_task: taskMgr.remove(self.__interest_timeout_task) self.__interest_timeout_task = None self.__interest_timeout_task = taskMgr.doMethodLater( self.network.interest_timeout, self._handle_interest_timeout, 'interest-timeout-%d' % self.channel) def send_client_done_set_zone_resp(self, zone_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_DONE_SET_ZONE_RESP) datagram.add_uint16(zone_id) self.handle_send_datagram(datagram) def send_client_get_state_resp(self, zone_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_STATE_RESP) datagram.pad_bytes(12) datagram.add_uint16(zone_id) self.handle_send_datagram(datagram) def handle_send_quiet_zone_resp(self, old_zone_id, new_zone_id): if not old_zone_id: self.send_client_done_set_zone_resp(new_zone_id) else: self.send_client_get_state_resp(new_zone_id) if old_zone_id and new_zone_id != OTP_ZONE_ID_OLD_QUIET_ZONE: self.send_client_done_set_zone_resp(new_zone_id) def handle_send_zone_resp(self, complete, old_zone_id, new_zone_id): if not complete: if not old_zone_id: self.send_client_done_set_zone_resp(new_zone_id) else: self.send_client_get_state_resp(new_zone_id) if old_zone_id and new_zone_id != OTP_ZONE_ID_OLD_QUIET_ZONE and complete: self.send_client_done_set_zone_resp(new_zone_id) def handle_set_zone_complete_callback(self, complete, old_parent_id, old_zone_id, new_parent_id, new_zone_id): if new_zone_id == OTP_ZONE_ID_OLD_QUIET_ZONE: if not complete: return self.handle_send_quiet_zone_resp(old_zone_id, new_zone_id) else: self.handle_send_zone_resp(complete, old_zone_id, new_zone_id) def handle_object_enter_owner(self, has_other, di): do_id = di.get_uint64() parent_id = di.get_uint64() zone_id = di.get_uint32() dc_id = di.get_uint16() datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_AVATAR_DETAILS_RESP) datagram.add_uint32(do_id) datagram.add_uint8(0) datagram.append_data(di.get_remaining_bytes()) self.handle_send_datagram(datagram) self._owned_objects.append(do_id) def _handle_interest_done(self): if self.__interest_timeout_task: taskMgr.remove(self.__interest_timeout_task) self.__interest_timeout_task = None if self._generate_deferred_callback: self._generate_deferred_callback.callback(True) self._generate_deferred_callback.destroy() self._generate_deferred_callback = None self._pending_objects = [] def _handle_interest_timeout(self, task): if len(self._pending_objects) > 0: self.notify.warning( 'Interest handle timed out for channel: %d, forcing completion...' % self.channel) self._handle_interest_done() return task.done def handle_object_enter_location(self, has_other, di): do_id = di.get_uint64() parent_id = di.get_uint64() zone_id = di.get_uint32() dc_id = di.get_uint16() if self.has_seen_object(do_id): return if do_id in self._owned_objects: return if not self._interest_manager.has_interest_zone(zone_id): return dclass = self.network.dc_loader.dclasses_by_number[dc_id] assert (dclass != None) # if there is a generate for the toon and it's zone is the quiet zone, # then we never allow that generate through... if dclass.get_name( ) == 'DistributedToon' and zone_id == OTP_ZONE_ID_OLD_QUIET_ZONE: return datagram = io.NetworkDatagram() if not has_other: datagram.add_uint16(types.CLIENT_CREATE_OBJECT_REQUIRED) else: datagram.add_uint16(types.CLIENT_CREATE_OBJECT_REQUIRED_OTHER) datagram.add_uint16(dc_id) datagram.add_uint32(do_id) datagram.append_data(di.get_remaining_bytes()) self.handle_send_datagram(datagram) seen_objects = self._seen_objects.setdefault(zone_id, []) seen_objects.append(do_id) # check to see if we have a pending interest handle that is looking # to see when this object generate has arrived. if do_id in self._pending_objects: self._pending_objects.remove(do_id) # if we have no more pending objects left to add, # then the interest timeout handle is now complete. if not self._pending_objects: self._handle_interest_done() return def send_client_object_delete_resp(self, do_id): datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_OBJECT_DELETE_RESP) datagram.add_uint32(do_id) self.handle_send_datagram(datagram) self.remove_seen_object(do_id) def handle_object_update_field(self, di): try: do_id = di.get_uint32() field_id = di.get_uint16() except: self.handle_send_disconnect( types.CLIENT_DISCONNECT_TRUNCATED_DATAGRAM, 'Received truncated datagram from channel: %d!' % self._channel) return datagram = io.NetworkDatagram() datagram.add_header(do_id, self.channel, types.STATESERVER_OBJECT_UPDATE_FIELD) datagram.add_uint32(do_id) datagram.add_uint16(field_id) datagram.append_data(di.get_remaining_bytes()) self.network.handle_send_connection_datagram(datagram) def handle_object_changing_location(self, di): do_id = di.get_uint32() new_parent_id = di.get_uint32() new_zone_id = di.get_uint32() # check to see if our owned object is being deleted, in this case # the shard our object is on, has been shutdown... if do_id in self._owned_objects and new_parent_id == 0 and new_zone_id == 0: self.handle_send_disconnect( types.CLIENT_DISCONNECT_SHARD_CLOSED, 'The shard you were playing on has been unexpectedly closed!') return if not self.has_seen_object(do_id): return if self._interest_manager.has_interest_zone(new_zone_id): return self.send_client_object_delete_resp(do_id) def handle_object_update_field_resp(self, di): do_id = di.get_uint32() field_id = di.get_uint16() # check to see if we either have seen this object's generate already, # or that the object is one of our owned objects... can_send_update = self.has_seen_object( do_id ) or do_id in self._pending_objects or do_id in self._owned_objects if not can_send_update: return datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_OBJECT_UPDATE_FIELD_RESP) datagram.add_uint32(do_id) datagram.add_uint16(field_id) datagram.append_data(di.get_remaining_bytes()) self.handle_send_datagram(datagram) def handle_object_delete_ram(self, di): do_id = di.get_uint32() if not self.has_seen_object(do_id): return self.send_client_object_delete_resp(do_id) def shutdown(self): if self.__interest_timeout_task: taskMgr.remove(self.__interest_timeout_task) self.__interest_timeout_task = None if self.network.account_manager.has_fsm(self.allocated_channel): self.network.account_manager.stop_operation(self) if self.allocated_channel: self.network.channel_allocator.free(self.allocated_channel) io.NetworkHandler.shutdown(self)
class LoadFriendsListFSM(ClientOperation): notify = notify.new_category('LoadAvatarFSM') def __init__(self, manager, client, callback, account_id, avatar_id): ClientOperation.__init__(self, manager, client, callback) self._account_id = account_id self._avatar_id = avatar_id self._dc_class = None self._fields = {} self._friends_list = {} self._pending_friends = [] def enterStart(self): def response(dclass, fields): if not dclass: self.cleanup(False) return self._dc_class = dclass self._fields = fields self.request('QueryFriends') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, response, self.manager.network.dc_loader.dclasses_by_name['DistributedToon']) def exitStart(self): pass def enterQueryFriends(self): friends_list, = self._fields['setFriendsList'] if not friends_list: self.cleanup(False) return self._pending_friends = { friend_id: friend_type for friend_id, friend_type in friends_list } for friend_id, friend_type in friends_list: def queryFriendCallback(dclass, fields, avatar_id=friend_id): if not dclass: self.cleanup(False) return self._friends_list[avatar_id] = [dclass, fields] del self._pending_friends[avatar_id] if not self._pending_friends: self.request('LoadFriends') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, friend_id, queryFriendCallback, self.manager.network.dc_loader. dclasses_by_name['DistributedToon']) def exitQueryFriends(self): pass def enterLoadFriends(self): our_channel = self.client.get_puppet_connection_channel( self._avatar_id) for friend_id in self._friends_list: friend_channel = self.client.get_puppet_connection_channel( friend_id) friend_online = self.manager.network.get_handler_from_channel( friend_channel) is not None # tell us if they are online or not... datagram = io.NetworkDatagram() if friend_online: datagram.add_uint16(types.CLIENT_FRIEND_ONLINE) else: datagram.add_uint16(types.CLIENT_FRIEND_OFFLINE) datagram.add_uint32(friend_id) self.client.handle_send_datagram(datagram) # tell them that we are online if they are online... if friend_online: datagram = io.NetworkDatagram() datagram.add_header(friend_channel, our_channel, types.CLIENTAGENT_FRIEND_ONLINE) datagram.add_uint32(self._avatar_id) self.manager.network.handle_send_connection_datagram(datagram) # setup a post remove that will tell all of our friends # that we are offline when we disconnect... post_remove = io.NetworkDatagram() post_remove.add_header(friend_channel, our_channel, types.CLIENTAGENT_FRIEND_OFFLINE) post_remove.add_uint32(self._avatar_id) datagram = io.NetworkDatagram() datagram.add_control_header(self.client.allocated_channel, types.CONTROL_ADD_POST_REMOVE) datagram.append_data(post_remove.get_message()) self.manager.network.handle_send_connection_datagram(datagram) datagram = io.NetworkDatagram() datagram.add_uint16(types.CLIENT_GET_FRIEND_LIST_RESP) datagram.add_uint8(0) datagram.add_uint16(len(self._friends_list)) for friend_id in self._friends_list: dclass, fields = self._friends_list[friend_id] datagram.add_uint32(friend_id) datagram.add_string(fields['setName'][0]) datagram.add_string(fields['setDNAString'][0]) self.client.handle_send_datagram(datagram) # we're all done. self.cleanup(True) def exitLoadFriends(self): pass
class DatabaseInterface(object): notify = notify.new_category('NetworkDatabaseInterface') def __init__(self, network): self._network = network self._context = 0 self._callbacks = {} self._dclasses = {} def get_context(self): self._context = (self._context + 1) & 0xFFFFFFFF return self._context def create_object(self, channel_id, database_id, dclass, fields={}, callback=None): """ Create an object in the specified database. database_id specifies the control channel of the target database. dclass specifies the class of the object to be created. fields is a dict with any fields that should be stored in the object on creation. callback will be called with callback(do_id) if specified. On failure, do_id is 0. """ # Save the callback: ctx = self.get_context() self._callbacks[ctx] = callback # Pack up/count valid fields. field_packer = DCPacker() field_count = 0 for k,v in fields.items(): field = dclass.get_field_by_name(k) if not field: self.notify.error('Creation request for %s object contains an invalid field named %s' % ( dclass.get_name(), k)) field_packer.raw_pack_uint16(field.get_number()) field_packer.begin_pack(field) field.pack_args(field_packer, v) field_packer.end_pack() field_count += 1 # Now generate and send the datagram: dg = io.NetworkDatagram() dg.add_header(database_id, channel_id, types.DBSERVER_CREATE_OBJECT) dg.add_uint32(ctx) dg.add_uint16(dclass.get_number()) dg.add_uint16(field_count) dg.append_data(field_packer.get_string()) self._network.handle_send_connection_datagram(dg) def handle_create_object_resp(self, di): ctx = di.get_uint32() do_id = di.get_uint32() if ctx not in self._callbacks: self.notify.warning('Received unexpected DBSERVER_CREATE_OBJECT_RESP (ctx %d, do_id %d)' % ( ctx, do_id)) return if self._callbacks[ctx]: self._callbacks[ctx](do_id) del self._callbacks[ctx] def query_object(self, channel_id, database_id, do_id, callback, dclass=None, field_names=()): """ Query object `do_id` out of the database. On success, the callback will be invoked as callback(dclass, fields) where dclass is a DCClass instance and fields is a dict. On failure, the callback will be invoked as callback(None, None). """ # Save the callback: ctx = self.get_context() self._callbacks[ctx] = callback self._dclasses[ctx] = dclass # Generate and send the datagram: dg = io.NetworkDatagram() if not field_names: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_GET_ALL) else: # We need a dclass in order to convert the field names into field IDs: assert dclass is not None if len(field_names) > 1: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_GET_FIELDS) else: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_GET_FIELD) dg.add_uint32(ctx) dg.add_uint32(do_id) if len(field_names) > 1: dg.add_uint16(len(field_names)) for field_name in field_names: field = dclass.get_field_by_name(field_name) if field is None: self.notify.error('Bad field named %s in query for %s object' % ( field_name, dclass.get_name())) dg.add_uint16(field.get_number()) self._network.handle_send_connection_datagram(dg) def handle_query_object_resp(self, message_type, di): ctx = di.get_uint32() success = di.get_uint8() if ctx not in self._callbacks: self.notify.warning('Received unexpected %s (ctx %d)' % (message_type, ctx)) return try: if not success: if self._callbacks[ctx]: self._callbacks[ctx](None, None) return if message_type == types.DBSERVER_OBJECT_GET_ALL_RESP: dclass_id = di.get_uint16() dclass = self._network.dc_loader.dclasses_by_number.get(dclass_id) else: dclass = self._dclasses[ctx] if not dclass: self.notify.error('Received bad dclass %d in DBSERVER_OBJECT_GET_ALL_RESP' % ( dclass_id)) if message_type == types.DBSERVER_OBJECT_GET_FIELD_RESP: field_count = 1 else: field_count = di.get_uint16() field_packer = DCPacker() field_packer.set_unpack_data(di.get_remaining_bytes()) fields = {} for x in xrange(field_count): field_id = field_packer.raw_unpack_uint16() field = dclass.get_field_by_index(field_id) if not field: self.notify.error('Received bad field %d in query for %s object' % ( field_id, dclass.get_name())) field_packer.begin_unpack(field) fields[field.get_name()] = field.unpack_args(field_packer) field_packer.end_unpack() if self._callbacks[ctx]: self._callbacks[ctx](dclass, fields) finally: del self._callbacks[ctx] del self._dclasses[ctx] def update_object(self, channel_id, database_id, do_id, dclass, new_fields, old_fields=None, callback=None): """ Update field(s) on an object, optionally with the requirement that the fields must match some old value. database_id and do_id represent the database control channel and object ID for the update request. new_fields is to be a dict of fieldname->value, representing the fields to add/change on the database object. old_fields, if specified, is a similarly-formatted dict that contains the expected older values. If the values do not match, the database will refuse to process the update. This is useful for guarding against race conditions. On success, the callback is called as callback(None). On failure, the callback is called as callback(dict), where dict contains the current object values. This is so that the updater can try again, basing its updates off of the new values. """ # Ensure that the keys in new_fields and old_fields are the same if # old_fields is given... if old_fields is not None: if set(new_fields.keys()) != set(old_fields.keys()): self.notify.error('new_fields and old_fields must contain the same keys!') return field_packer = DCPacker() field_count = 0 for k,v in new_fields.items(): field = dclass.get_field_by_name(k) if not field: self.notify.error('Update for %s(%d) object contains invalid field named %s' % ( dclass.get_name(), do_id, k)) field_packer.raw_pack_uint16(field.get_number()) if old_fields is not None: # Pack the old values: field_packer.begin_pack(field) field.pack_args(field_packer, old_fields[k]) field_packer.end_pack() field_packer.begin_pack(field) field.pack_args(field_packer, v) field_packer.end_pack() field_count += 1 # Generate and send the datagram: dg = io.NetworkDatagram() if old_fields is not None: ctx = self.get_context() self._callbacks[ctx] = callback if field_count == 1: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_SET_FIELD_IF_EQUALS) else: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS) dg.add_uint32(ctx) else: if field_count == 1: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_SET_FIELD) else: dg.add_header(database_id, channel_id, types.DBSERVER_OBJECT_SET_FIELDS) dg.add_uint32(do_id) if field_count != 1: dg.add_uint16(field_count) dg.append_data(field_packer.getString()) self._network.handle_send_connection_datagram(dg) if old_fields is None and callback is not None: # Why oh why did they ask for a callback if there's no old_fields? # Oh well, better honor their request: callback(None) def handle_update_object_resp(self, di, multi): ctx = di.get_uint32() success = di.get_uint8() if ctx not in self._callbacks: self.notify.warning('Received unexpected DBSERVER_OBJECT_SET_FIELD(S)_IF_EQUALS_RESP (ctx %d)' % ( ctx)) return try: if success: if self._callbacks[ctx]: self._callbacks[ctx](None) return if not di.get_remaining_size(): # We failed due to other reasons. if self._callbacks[ctx]: return self._callbacks[ctx]({}) if multi: field_count = di.get_uint16() else: field_count = 1 field_packer = DCPacker() field_packer.set_unpack_data(di.get_remaining_bytes()) fields = {} for x in xrange(field_count): fieldId = field_packer.raw_pack_uint16() field = self._network.dc_loader.dc_file.get_field_by_index(fieldId) if not field: self.notify.error('Received bad field %d in update failure response message' % ( fieldId)) field_packer.begin_unpack(field) fields[field.get_name()] = field.unpack_args(field_packer) field_packer.end_unpack() if self._callbacks[ctx]: self._callbacks[ctx](fields) finally: del self._callbacks[ctx] def handle_datagram(self, message_type, di): if message_type == types.DBSERVER_CREATE_OBJECT_RESP: self.handle_create_object_resp(di) elif message_type in (types.DBSERVER_OBJECT_GET_ALL_RESP, types.DBSERVER_OBJECT_GET_FIELDS_RESP, types.DBSERVER_OBJECT_GET_FIELD_RESP): self.handle_query_object_resp(message_type, di) elif message_type == types.DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP: self.handle_update_object_resp(di, False) elif message_type == types.DBSERVER_OBJECT_SET_FIELDS_IF_EQUALS_RESP: self.handle_update_object_resp(di, True)
class StateObjectManager(object): notify = notify.new_category('StateObjectManager') def __init__(self): self.objects = {} def has_object(self, do_id): return do_id in self.objects def add_object(self, state_object): if self.has_object(state_object.do_id): return self.objects[state_object.do_id] = state_object state_object.setup() def remove_object(self, state_object): if not self.has_object(state_object.do_id): return # kill off all of our child objects, in this case we do not want # to recurse our children's zone objects because this will be done # automatically here when we call `remove_object`... for child_object in state_object.get_all_zone_objects( recurse_children=False): self.remove_object(child_object) state_object.destroy() del self.objects[state_object.do_id] def get_object(self, do_id): return self.objects.get(do_id) def get_object_by_owner(self, owner_id): for state_object in list(self.objects.values()): if state_object.owner_id == owner_id: return state_object return None def handle_changing_location(self, state_object): assert (state_object != None) # tell the object's previous parent that we've moved away from under # them and are no longer in the previous location... if state_object.old_parent_id: state_object.handle_send_changing_location( state_object.old_parent_id) # also tell the object's new parent that they have changed locations under # them in a zone of their's... if state_object.parent_id: state_object.handle_send_changing_location(state_object.parent_id) def handle_updating_field(self, state_object, sender, field, field_args, excludes=[]): assert (state_object != None) if not state_object.parent_id: self.notify.warning('Cannot handle updating field for object: %d, ' 'object has no parent!' % state_object.do_id) return parent_object = self.get_object(state_object.parent_id) if not parent_object: self.notify.warning('Cannot handle updating field for object: %d, ' 'object has no parent!' % state_object.do_id) return if not parent_object.has_child(state_object.do_id): self.notify.debug('Cannot handle updating field for object: %d, ' 'we: %d are not this objects parent!' % (state_object.do_id, parent_object.do_id)) return child_zone_id = parent_object.get_zone_from_child(state_object.do_id) for zone_object in itertools.ifilter( lambda x: x.owner_id > 0 and x.do_id not in excludes, parent_object.get_all_zone_objects()): state_object.handle_send_update_field(zone_object.owner_id, state_object.do_id, field, field_args)
class StateServer(io.NetworkConnector, component.Component): notify = notify.new_category('StateServer') def __init__(self, dc_loader): connect_address = config.GetString('stateserver-connect-address', '127.0.0.1') connect_port = config.GetInt('stateserver-connect-port', 7100) io.NetworkConnector.__init__(self, dc_loader, connect_address, connect_port) self._channel = config.GetInt('stateserver-channel', types.STATESERVER_CHANNEL) self.shard_manager = ShardManager() self.object_manager = StateObjectManager() def handle_datagram(self, channel, sender, message_type, di): if message_type == types.STATESERVER_ADD_SHARD: self.handle_add_shard(sender, di) elif message_type == types.STATESERVER_UPDATE_SHARD: self.handle_update_shard(sender, di) elif message_type == types.STATESERVER_REMOVE_SHARD: self.handle_remove_shard(sender) elif message_type == types.STATESERVER_GET_SHARD_ALL: self.handle_send_shard_list(sender) elif message_type == types.STATESERVER_OBJECT_GENERATE_WITH_REQUIRED: self.handle_generate(sender, False, di) elif message_type == types.STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER: self.handle_generate(sender, True, di) elif message_type == types.STATESERVER_OBJECT_UPDATE_FIELD: self.handle_object_update_field(channel, sender, di) elif message_type == types.STATESERVER_OBJECT_DELETE_RAM: self.handle_delete_object(sender, di) else: self.handle_object_datagram(channel, sender, message_type, di) def handle_object_datagram(self, channel, sender, message_type, di): state_object = self.object_manager.get_object(channel) if not state_object: self.notify.warning('Received an unknown message type: ' '%d from channel: %d!' % (message_type, sender)) return state_object.handle_internal_datagram(sender, message_type, di) def handle_add_shard(self, ai_channel, di): shard = self.shard_manager.add_shard(ai_channel, di.get_uint32(), di.get_string(), di.get_uint32()) # tell everyone that this shard is up and then update the shard info self.handle_broadcast_server_up(shard) self.handle_broadcast_shard_info() # setup a post remove to remove the shard when the AI disconnects post_remove = io.NetworkDatagram() post_remove.add_header(self._channel, shard.channel, types.STATESERVER_REMOVE_SHARD) datagram = io.NetworkDatagram() datagram.add_control_header(shard.channel, types.CONTROL_ADD_POST_REMOVE) datagram.append_data(post_remove.get_message()) self.handle_send_connection_datagram(datagram) def handle_update_shard(self, ai_channel, di): shard = self.shard_manager.get_shard(ai_channel) if not shard: self.notify.warning( 'Cannot update shard: %d, shard does not exist!' % ai_channel) return shard.name = di.get_string() shard.population = di.get_uint32() # tell everyone about the shard's new info self.handle_broadcast_shard_info() def handle_remove_shard(self, ai_channel): shard = self.shard_manager.get_shard(ai_channel) if not shard: self.notify.debug( 'Cannot remove shard: %d, shard does not exist!' % ai_channel) return shard_object = self.object_manager.get_object(shard.district_id) if not shard_object: self.notify.warning( 'Failed to delete all shard objects for shard: ' '%d with unknown district object: %d!' % (shard.channel, shard.district_id)) return self.object_manager.remove_object(shard_object) self.shard_manager.remove_shard(shard) # tell everyone that this shard has been closed self.handle_broadcast_server_down(shard) def handle_send_shard_list(self, channel): datagram = io.NetworkDatagram() datagram.add_header(channel, self.channel, types.STATESERVER_GET_SHARD_ALL_RESP) datagram.add_uint16(len(self.shard_manager.shards)) for shard in list(self.shard_manager.shards.values()): datagram.add_uint32(shard.channel) datagram.add_string(shard.name) datagram.add_uint32(shard.population) self.handle_send_connection_datagram(datagram) def handle_send_server_up(self, channel, shard): assert (shard is not None) datagram = io.NetworkDatagram() datagram.add_header(channel, self.channel, types.STATESERVER_SERVER_UP) datagram.add_uint32(shard.channel) datagram.add_string(shard.name) self.handle_send_connection_datagram(datagram) def handle_send_server_down(self, channel, shard): assert (shard is not None) datagram = io.NetworkDatagram() datagram.add_header(channel, self.channel, types.STATESERVER_SERVER_DOWN) datagram.add_uint32(shard.channel) self.handle_send_connection_datagram(datagram) def handle_broadcast_server_up(self, shard): for state_object in itertools.ifilter( lambda x: x.owner_id > 0, list(self.object_manager.objects.values())): self.handle_send_server_up(state_object.owner_id, shard) def handle_broadcast_server_down(self, shard): for state_object in itertools.ifilter( lambda x: x.owner_id > 0, list(self.object_manager.objects.values())): self.handle_send_server_down(state_object.owner_id, shard) def handle_broadcast_shard_info(self): for state_object in itertools.ifilter( lambda x: x.owner_id > 0, list(self.object_manager.objects.values())): self.handle_send_shard_list(state_object.owner_id) def handle_generate(self, sender, has_other, di): do_id = di.get_uint32() parent_id = di.get_uint32() zone_id = di.get_uint32() dc_id = di.get_uint16() if self.object_manager.has_object(do_id): self.notify.info('Failed to generate an already existing ' 'object with do_id: %d!' % do_id) return dc_class = self.dc_loader.dclasses_by_number.get(dc_id) if not dc_class: self.notify.warning( 'Failed to generate an object with do_id: %d, ' 'no dclass found for dc_id: %d!' % do_id, dc_id) return state_object = StateObject(self, self.object_manager, sender, do_id, parent_id, zone_id, dc_class, has_other, di) self.object_manager.add_object(state_object) def handle_object_update_field(self, channel, sender, di): do_id = di.get_uint32() if not di.get_remaining_size(): self.notify.warning( 'Cannot handle an field update for object: %d, ' 'truncated datagram!' % do_id) return state_object = self.object_manager.get_object(do_id) if not state_object: self.notify.debug('Cannot handle an field update for object: %d, ' 'unknown object!' % do_id) return state_object.handle_update_field(channel, sender, di) def handle_delete_object(self, sender, di): do_id = di.get_uint32() state_object = self.object_manager.get_object(do_id) if not state_object: self.notify.debug( 'Failed to delete object: %d, object does not exist!' % do_id) return self.object_manager.remove_object(state_object)
class LoadAccountFSM(ClientOperation): notify = notify.new_category('LoadAccountFSM') def __init__(self, manager, client, callback, play_token): ClientOperation.__init__(self, manager, client, callback) self._play_token = play_token self._account_id = None def enterStart(self): if self._play_token not in self.manager.dbm: self.demand('Create') return self._account_id = int(self.manager.dbm[self._play_token]) self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._account_id, self.__account_loaded, self.manager.network.dc_loader.dclasses_by_name['Account']) def __account_loaded(self, dclass, fields): if not dclass: self.notify.warning( 'Failed to load account: %d for channel: ' '%d playtoken: %s!' % (self._account_id, self._client.channel, self._play_token)) self.cleanup(False) return self.request('SetAccount') def exitStart(self): pass def enterCreate(self): fields = { 'ACCOUNT_AV_SET': ([0] * 6, ), 'BIRTH_DATE': ('', ), 'BLAST_NAME': (self._play_token, ), 'CREATED': (time.ctime(), ), 'FIRST_NAME': ('', ), 'LAST_LOGIN': ('', ), 'LAST_NAME': ('', ), 'PLAYED_MINUTES': ('', ), 'PLAYED_MINUTES_PERIOD': ('', ), 'HOUSE_ID_SET': ([0] * 6, ), 'ESTATE_ID': (0, ) } self.manager.network.database_interface.create_object( self.client.channel, types.DATABASE_CHANNEL, self.manager.network.dc_loader.dclasses_by_name['Account'], fields=fields, callback=self.__account_created) def __account_created(self, account_id): self._account_id = account_id if not self._account_id: self.notify.warning('Failed to create account for channel: ' '%d playtoken: %s!' % (self._client.channel, self._play_token)) self.cleanup(False) return self.manager.dbm[self._play_token] = str(self._account_id) self.manager.dbm.sync() self.request('SetAccount') def exitCreate(self): pass def enterSetAccount(self): # mark them as been authenticated self._client.authenticated = True # add this connection to the account channel channel = self.client.get_account_connection_channel(self._account_id) self.client.register_for_channel(channel) # add them to the account channel channel = self._account_id << 32 self.client.handle_set_channel_id(channel) # we're all done. self.cleanup(True, self._play_token) def exitSetAccount(self): pass
class StateObject(object): notify = notify.new_category('StateObject') def __init__(self, network, object_manager, ai_channel, do_id, parent_id, zone_id, dc_class, has_other, di): self._network = network self.object_manager = object_manager self._do_id = do_id self._old_ai_channel = 0 self._ai_channel = ai_channel self._old_owner_id = 0 self._owner_id = 0 self._old_parent_id = 0 self._parent_id = parent_id self._old_zone_id = 0 self._zone_id = zone_id self._dc_class = dc_class self._has_other = has_other self._required_fields = {} self._other_fields = {} self._zone_objects = {} field_packer = DCPacker() field_packer.set_unpack_data(di.get_remaining_bytes()) # unpack all of the initial "required" fields for field_index in xrange(self._dc_class.get_num_inherited_fields()): field = self._dc_class.get_inherited_field(field_index) if not field: self.notify.error('Failed to unpack required field: %d ' 'dclass: %s, unknown field!' % (field_index, self._dc_class.get_name())) if not field.is_required(): continue field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() self._required_fields[field.get_number()] = field_args # unpack all of the initial "other" fields, if they are specified if self._has_other: num_fields = field_packer.raw_unpack_uint16() for _ in xrange(num_fields): field_id = field_packer.raw_unpack_uint16() field = self._dc_class.get_field_by_index(field_id) if not field: self.notify.error('Failed to unpack other field: %d ' 'dclass: %s, unknown field!' % (field_id, self._dc_class.get_name())) if not field.is_ram(): continue field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() self._other_fields[field.get_number()] = field_args self._network.register_for_channel(self._do_id) @property def do_id(self): return self._do_id @property def old_ai_channel(self): return self._old_ai_channel @property def ai_channel(self): return self._ai_channel @ai_channel.setter def ai_channel(self, ai_channel): self._old_ai_channel = self._ai_channel self._ai_channel = ai_channel @property def old_owner_id(self): return self._old_owner_id @property def owner_id(self): return self._owner_id @owner_id.setter def owner_id(self, owner_id): self._old_owner_id = self._owner_id self._owner_id = owner_id @property def old_parent_id(self): return self._old_parent_id @property def parent_id(self): return self._parent_id @parent_id.setter def parent_id(self, parent_id): self._old_parent_id = self._parent_id self._parent_id = parent_id @property def old_zone_id(self): return self._old_zone_id @property def zone_id(self): return self._zone_id @zone_id.setter def zone_id(self, zone_id): self._old_zone_id = self._zone_id self._zone_id = zone_id @property def dc_class(self): return self._dc_class @property def has_other(self): return self._has_other def __repr__(self): return '%s(%d): ai_channel: %d, owner_id: %d, parent_id: %d, zone_id: %d' % ( self._dc_class.get_name(), self._do_id, self._ai_channel, self._owner_id, self._parent_id, self._zone_id) def has_child(self, child_do_id): return self.get_zone_from_child(child_do_id) is not None def has_child_in_zone(self, child_do_id, zone_id): return self.get_zone_from_child(child_do_id) == zone_id def add_child_in_zone(self, child_do_id, zone_id): zone_objects = self._zone_objects.setdefault(zone_id, []) zone_objects.append(child_do_id) def remove_child_from_zone(self, child_do_id, zone_id): zone_objects = self._zone_objects.get(zone_id, None) assert (zone_objects != None) if child_do_id in zone_objects: zone_objects.remove(child_do_id) if not len(zone_objects): del self._zone_objects[zone_id] def get_zone_from_child(self, child_do_id): for zone_id, zone_objects in list(self._zone_objects.items()): if child_do_id in zone_objects: return zone_id return None def get_zone_objects(self, zone_id): if zone_id not in self._zone_objects: return [] zone_objects = [] for do_id in list(self._zone_objects[zone_id]): zone_object = self._network.object_manager.get_object(do_id) if not zone_object: continue zone_objects.append(zone_object) return zone_objects def get_all_zone_objects(self, recurse_children=True): zone_objects = [] for zone_id in list(self._zone_objects): zone_objects.extend(self.get_zone_objects(zone_id)) # also get all of our children's zone objects if recurse_children: for zone_object in list(zone_objects): zone_objects.extend(zone_object.get_all_zone_objects()) return zone_objects def get_zones_objects(self, zone_ids): zone_objects = [] for zone_id in zone_ids: zone_objects.extend(self.get_zone_objects(zone_id)) return zone_objects def append_required_data(self, datagram, broadcast_only=True): sorted_fields = collections.OrderedDict( sorted(self._required_fields.items())) field_packer = DCPacker() for field_index, field_args in sorted_fields.items(): field = self._dc_class.get_field_by_index(field_index) if not field: self.notify.error( 'Failed to append required data for field: %d ' 'dclass: %s, unknown field!' % (field_index, self._dc_class.get_name())) if broadcast_only and not field.is_broadcast(): continue field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.append_data(field_packer.get_string()) def append_other_data(self, datagram): field_packer = DCPacker() for field_index, field_args in list(self._other_fields.items()): field = self._dc_class.get_field_by_index(field_index) if not field: self.notify.error('Failed to append other data for field: %d ' 'dclass: %s, unknown field!' % (field_index, self._dc_class.get_name())) field_packer.raw_pack_uint16(field.get_number()) field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.add_uint16(len(self._other_fields)) datagram.append_data(field_packer.get_string()) def setup(self): self.object_manager.handle_changing_location(self) def handle_internal_datagram(self, sender, message_type, di): if message_type == types.STATESERVER_OBJECT_SET_OWNER: self.handle_set_owner(sender, di) elif message_type == types.STATESERVER_OBJECT_SET_AI: self.handle_set_ai(sender, di) elif message_type == types.STATESERVER_OBJECT_SET_ZONE: self.handle_set_zone(sender, di) elif message_type == types.STATESERVER_OBJECT_CHANGING_LOCATION: self.handle_changing_location(di.get_uint32(), di.get_uint32(), di.get_uint32()) elif message_type == types.STATESERVER_OBJECT_SET_LOCATION: self.handle_set_location(sender, di) elif message_type == types.STATESERVER_OBJECT_GET_ZONES_OBJECTS: self.handle_get_zones_objects(sender, di) else: self.notify.warning('Received unknown message type: %d ' 'for object %d!' % (message_type, self._do_id)) return def handle_send_changing_owner(self, channel, old_owner_id, new_owner_id): datagram = io.NetworkDatagram() datagram.add_header(channel, self._do_id, types.STATESERVER_OBJECT_CHANGING_OWNER) datagram.add_uint64(self._do_id) datagram.add_uint64(new_owner_id) datagram.add_uint64(old_owner_id) self._network.handle_send_connection_datagram(datagram) def handle_send_owner_entry(self, channel): datagram = io.NetworkDatagram() if not self._has_other: datagram.add_header( channel, self._do_id, types.STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED) else: datagram.add_header( channel, self._do_id, types.STATESERVER_OBJECT_ENTER_OWNER_WITH_REQUIRED_OTHER) datagram.add_uint64(self._do_id) datagram.add_uint64(self._parent_id) datagram.add_uint32(self._zone_id) datagram.add_uint16(self._dc_class.get_number()) self.append_required_data(datagram, broadcast_only=False) if self._has_other: self.append_other_data(datagram) self._network.handle_send_connection_datagram(datagram) def handle_set_owner(self, sender, di): new_owner_id = di.get_uint64() if new_owner_id == self._owner_id: return self.owner_id = new_owner_id self.handle_send_owner_entry(self._owner_id) self.handle_send_changing_owner(self._old_owner_id, self._old_owner_id, self._owner_id) def handle_send_changing_ai(self, channel): datagram = io.NetworkDatagram() datagram.add_header(channel, self._do_id, types.STATESERVER_OBJECT_CHANGING_AI) datagram.add_uint64(self._do_id) datagram.add_uint64(self._old_ai_channel) datagram.add_uint64(self._ai_channel) self._network.handle_send_connection_datagram(datagram) def handle_send_ai_entry(self, ai_channel): datagram = io.NetworkDatagram() if not self._has_other: datagram.add_header( ai_channel, self._do_id, types.STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED) else: datagram.add_header( ai_channel, self._do_id, types.STATESERVER_OBJECT_ENTER_AI_WITH_REQUIRED_OTHER) datagram.add_uint64(self._do_id) datagram.add_uint64(self._parent_id) datagram.add_uint32(self._zone_id) datagram.add_uint16(self._dc_class.get_number()) self.append_required_data(datagram, broadcast_only=not self._owner_id) if self._has_other: self.append_other_data(datagram) self._network.handle_send_connection_datagram(datagram) def handle_set_ai(self, sender, di): new_ai_channel = di.get_uint64() if new_ai_channel == self._ai_channel: return shard = self._network.shard_manager.get_shard(new_ai_channel) if not shard: self.notify.warning('Failed to set new AI: %d for object %d, ' 'no AI was found with that channel!' % (new_ai_channel, self._do_id)) return self.ai_channel = new_ai_channel if self._owner_id: self.parent_id = shard.district_id self.handle_send_ai_entry(self._ai_channel) self.handle_send_changing_ai(self.old_ai_channel) self.object_manager.handle_changing_location(self) def handle_send_changing_location(self, channel): datagram = io.NetworkDatagram() datagram.add_header(channel, self._do_id, types.STATESERVER_OBJECT_CHANGING_LOCATION) datagram.add_uint32(self._do_id) datagram.add_uint32(self._parent_id) datagram.add_uint32(self._zone_id) self._network.handle_send_connection_datagram(datagram) def handle_set_zone(self, sender, di): new_zone_id = di.get_uint32() self.zone_id = new_zone_id self.handle_send_changing_location(self._ai_channel) self.object_manager.handle_changing_location(self) def handle_send_location_entry(self, channel): datagram = io.NetworkDatagram() if not self._has_other: datagram.add_header( channel, self._do_id, types.STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED) else: datagram.add_header( channel, self._do_id, types.STATESERVER_OBJECT_ENTER_LOCATION_WITH_REQUIRED_OTHER) datagram.add_uint64(self._do_id) datagram.add_uint64(self._parent_id) datagram.add_uint32(self._zone_id) datagram.add_uint16(self._dc_class.get_number()) self.append_required_data(datagram) if self._has_other: self.append_other_data(datagram) self._network.handle_send_connection_datagram(datagram) def handle_send_departure(self, channel): datagram = io.NetworkDatagram() datagram.add_header(channel, self._do_id, types.STATESERVER_OBJECT_DELETE_RAM) datagram.add_uint32(self._do_id) self._network.handle_send_connection_datagram(datagram) def handle_send_object_location_ack(self, channel): datagram = io.NetworkDatagram() datagram.add_header(channel, self._do_id, types.STATESERVER_OBJECT_LOCATION_ACK) datagram.add_uint32(self._do_id) datagram.add_uint32(self._old_parent_id) datagram.add_uint32(self._old_zone_id) datagram.add_uint32(self._parent_id) datagram.add_uint32(self._zone_id) self._network.handle_send_connection_datagram(datagram) def handle_changing_location(self, child_do_id, new_parent_id, new_zone_id): # retrieve this object from it's do_id, if we cannot find this object in the do_id to do # dictionary, then this is an invalid object... child_object = self.object_manager.get_object(child_do_id) if not child_object: return send_location_departure = False send_location_entry = False child_zone_id = 0 if self.has_child(child_object.do_id): child_zone_id = self.get_zone_from_child(child_object.do_id) if new_parent_id != self._do_id: self.remove_child_from_zone(child_object.do_id, child_zone_id) send_location_departure = True elif new_zone_id != child_zone_id: self.remove_child_from_zone(child_object.do_id, child_zone_id) self.add_child_in_zone(child_object.do_id, new_zone_id) send_location_departure = True send_location_entry = True else: self.add_child_in_zone(child_object.do_id, new_zone_id) send_location_entry = True for zone_object in itertools.ifilter(lambda x: x.owner_id > 0, self.get_all_zone_objects()): if send_location_departure: child_object.handle_send_changing_location( zone_object.owner_id) if send_location_entry: child_object.handle_send_location_entry(zone_object.owner_id) # acknowledge the object's location change if child_object.owner_id: child_object.handle_send_object_location_ack(child_object.owner_id) def handle_set_location(self, sender, di): new_parent_id = di.get_uint32() new_zone_id = di.get_uint32() if new_parent_id == self._parent_id and new_zone_id == self._zone_id: return self.parent_id = new_parent_id self.zone_id = new_zone_id self.object_manager.handle_changing_location(self) def handle_get_zones_objects(self, sender, di): zone_ids = [di.get_uint32() for _ in xrange(di.get_uint16())] if not self._owner_id: self.notify.warning('Cannot get zone objects for object: %d, ' 'object does not have an owner!' % self._do_id) return parent_object = self._network.object_manager.get_object( self._parent_id) if not parent_object: self.notify.warning('Cannot get zone objects for object: %d, ' 'object has no parent!' % self._do_id) return # filter out our own object from the zone list, as we do not want # to send our own object because we have reference to it locally... zone_objects = list( itertools.ifilter( lambda x: x.do_id != self._do_id and x.owner_id != self. _owner_id, parent_object.get_zones_objects(zone_ids))) # tell the Client Agent that the they should expect this # many objects to have been generated before completing the zone change... datagram = io.NetworkDatagram() datagram.add_header(self._owner_id, self._do_id, types.STATESERVER_OBJECT_GET_ZONES_OBJECTS_RESP) datagram.add_uint64(self._do_id) datagram.add_uint16(len(zone_objects)) for zone_object in zone_objects: datagram.add_uint64(zone_object.do_id) self._network.handle_send_connection_datagram(datagram) # finally once we've sent the objects we expect the client, # to see before completing the interest change, start sending object generates... for zone_object in zone_objects: zone_object.handle_send_location_entry(self._owner_id) def handle_send_update_field(self, channel, sender, field, field_args): datagram = io.NetworkDatagram() datagram.add_header(channel, sender, types.STATESERVER_OBJECT_UPDATE_FIELD) datagram.add_uint32(self._do_id) datagram.add_uint16(field.get_number()) field_packer = DCPacker() field_packer.begin_pack(field) if field_args is not None: field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.append_data(field_packer.get_string()) self._network.handle_send_connection_datagram(datagram) def handle_send_save_field(self, field, field_args): datagram = io.NetworkDatagram() datagram.add_header(types.DATABASE_CHANNEL, self._do_id, types.DBSERVER_OBJECT_SET_FIELD) datagram.add_uint32(self._do_id) field_packer = DCPacker() field_packer.raw_pack_uint16(field.get_number()) field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.append_data(field_packer.get_string()) self._network.handle_send_connection_datagram(datagram) def handle_update_field(self, channel, sender, di): field_id = di.get_uint16() field = self._dc_class.get_field_by_index(field_id) if not field: self.notify.warning('Failed to update field: %d dclass: %s, ' 'unknown field!' % (field_id, self._dc_class.get_name())) return datagram = io.NetworkDatagram() datagram.append_data(di.get_remaining_bytes()) di = io.NetworkDatagramIterator(datagram) # if the iterator is empty, this means that the field # has no arguents and that we should not attempt to update it... if di.get_remaining_size(): field_packer = DCPacker() field_packer.set_unpack_data(di.get_remaining_bytes()) try: field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() except RuntimeError: # apparently we failed to unpack the arguments for # this field we recieved, ignore the update... return else: field_args = None di = io.NetworkDatagramIterator(datagram) # check to ensure that the sender of this update is either an owner # of an existing object, or that they are an AI shard channel: state_object = self._network.object_manager.get_object_by_owner(sender) if state_object is not None: avatar_id = self._network.get_avatar_id_from_connection_channel( sender) if not avatar_id: self.notify.warning( 'Cannot handle field update for field: %s dclass: %s, ' 'unknown avatar: %d!' % (field.get_name(), self._dc_class.get_name(), avatar_id)) return # check to ensure the client can send this field, if the field # is marked ownsend; the field can only be sent by the owner of the object, # if the field is marked clsend the field is sendable always. Otherwise # if the client sends the field and it is not marked either of these, # the field update is invalid and the field is not sendable by a client... if field.is_ownsend(): if sender != self._owner_id: self.notify.warning( 'Cannot handle field update for field: %s ' 'dclass: %s, field not sendable!' % (field.get_name(), self._dc_class.get_name())) return else: if not field.is_clsend(): self.notify.warning( 'Cannot handle field update for field: %s ' 'dclass: %s, field not sendable!' % (field.get_name(), self._dc_class.get_name())) return # we must always send this update to the other receiver, # so that they get the field update always even if the field # is broadcasted to other objects in the same interest... self.handle_send_update_field(self._ai_channel, sender, field, field_args) # if the field is marked broadcast, then we can proceed to broadcast # this field to any other objects in our interest. if field.is_broadcast(): self.object_manager.handle_updating_field(self, sender, field, field_args, excludes=[avatar_id]) if field_args is not None: # the client has sent an broadcast field that is marked ram, # store this field since it passes both the is clsend or is ownsend tests... if field.is_ram(): # ensure the object the client sent the field update for # has other fields... if not self._has_other: return # check to see if this field is a required field, if it is then # this means it should be stored as a required field.... if field.is_required(): self._required_fields[field.get_number()] = field_args else: self._other_fields[field.get_number()] = field_args else: if not self._network.shard_manager.has_shard(sender): self.notify.warning( 'Cannot handle field update for field: %s dclass: %s, ' 'unknown sender with channel: %d!' % (field.get_name(), self._dc_class.get_name(), sender)) return # we must always send this update to the other receiver, # so that they get the field update always even if the field # is broadcasted to other objects in the same interest... self.handle_send_update_field(self._owner_id, self._ai_channel, field, field_args) # if the field is marked broadcast, then we can proceed to broadcast # this field to any other objects in our interest. if field.is_broadcast(): self.object_manager.handle_updating_field( self, self._parent_id, field, field_args, excludes=[self.do_id]) if field_args is not None: # if the AI object sends specifically other (ram) fields for this object, # this means the object now has other fields... if field.is_ram(): # check to see if this field is a required field, if it is then # this means it should be stored as a required field.... if field.is_required(): self._required_fields[field.get_number()] = field_args else: self._other_fields[field.get_number()] = field_args # the object now has other fields, let's update the object's has_other # value so that generates will be sent including the other fields... self._has_other = True # check to see if the field is marked db, this means that we send the field # to the database to override any current fields with that value... if field.is_db(): self.handle_send_save_field(field, field_args) def destroy(self): self.parent_id = 0 self.zone_id = 0 self.handle_send_departure(self._ai_channel) old_parent = self.object_manager.get_object(self._old_parent_id) if old_parent is not None: old_parent.handle_changing_location(self._do_id, self._parent_id, self._zone_id) # kill our object on the ClientAgent if we are a client owned object if self._owner_id: self.handle_send_changing_location(self._owner_id) self._network.unregister_for_channel(self._do_id) self.owner_id = 0 self._required_fields = {} self._other_fields = {} self._zone_objects = {}
class DatabaseCreateFSM(DatabaseOperationFSM): notify = notify.new_category('DatabaseCreateFSM') def __init__(self, *args, **kwargs): self._context = kwargs.pop('context', 0) self._dc_id = kwargs.pop('dc_id', 0) self._field_count = kwargs.pop('field_count', 0) self._field_data = kwargs.pop('field_data', None) DatabaseOperationFSM.__init__(self, *args, **kwargs) def enterStart(self): dc_class = self.network.dc_loader.dclasses_by_number.get(self._dc_id) if not dc_class: self.notify.error('Failed to create object: %d context: %d, ' 'unknown dclass!' % (self._dc_id, self._context)) self._do_id = self.network.backend.allocator.allocate() if not self._do_id: self.notify.warning('Failed to create object: %d context: %d, ' 'could not allocate new do_id!' % (self._dc_class, self._context)) self.cleanup(False, self._context, 0) return if self.network.backend.has_file('%d' % self._do_id): self.notify.warning( 'Failed to create object: %d context: %d, ' 'could not create new entry with same do_id: %d!' % (self._dc_class, self._context, self._do_id)) self.cleanup(False, self._context, 0) return file_object = self.network.backend.open_file('%d' % self._do_id) file_object.save() file_object.set_value('dclass', dc_class.get_name()) file_object.set_value('do_id', self._do_id) fields = {} field_packer = DCPacker() field_packer.set_unpack_data(self._field_data) for _ in xrange(self._field_count): field_id = field_packer.raw_unpack_uint16() field = dc_class.get_field_by_index(field_id) if not field: self.notify.error('Failed to unpack field: %d dclass: %s, ' 'invalid field!' % (field_id, dc_class.get_name())) field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() if not field_args: self.notify.error( 'Failed to unpack field args for field: %d dclass: %s, ' 'invalid result!' % (field.get_name(), dc_class.get_name())) fields[field.get_name()] = field_args for field_index in xrange(dc_class.get_num_inherited_fields()): field_packer = DCPacker() field = dc_class.get_inherited_field(field_index) if not field or field.get_name() in fields: continue if not field.is_db() or not field.has_default_value(): continue field_packer.set_unpack_data(field.get_default_value()) field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() if not field_args: self.notify.error( 'Failed to unpack field args for field: %d dclass: %s, ' 'invalid result!' % (field.get_name(), dc_class.get_name())) fields[field.get_name()] = field_args file_object.set_value('fields', fields) self.network.backend.close_file(file_object) self.network.backend.tracker.set_value('next', self._do_id + 1) self.cleanup(True, self._context, self._do_id) def exitStart(self): pass
class DatabaseSetFieldFSM(DatabaseOperationFSM): notify = notify.new_category('DatabaseSetFieldFSM') def __init__(self, *args, **kwargs): self._do_id = kwargs.pop('do_id', 0) self._context = kwargs.pop('context', 0) self._field_data = kwargs.pop('field_data', None) DatabaseOperationFSM.__init__(self, *args, **kwargs) def enterStart(self): file_object = self.network.backend.open_file('%d' % self._do_id) if not file_object: self.notify.warning('Failed to set fields for object: %d, ' 'unknown object!' % self._do_id) self.cleanup(False, self._context, None, None) return dc_name = file_object.get_value('dclass') dc_class = self.network.dc_loader.dclasses_by_name.get(dc_name) if not dc_class: self.notify.warning('Failed to set fields for object: %d, ' 'unknown dclass: %s!' % (self._do_id, dc_name)) self.cleanup(False, self._context, None, None) return field_packer = DCPacker() field_packer.set_unpack_data(self._field_data) field_id = field_packer.raw_unpack_uint16() field = dc_class.get_field_by_index(field_id) if not field: self.notify.error('Failed to unpack field: %d dclass: %s, ' 'invalid field!' % (field_id, dc_class.get_name())) fields = file_object.get_value('fields') if not fields: self.notify.warning('Failed to set fields for object: %d, ' 'invalid fields!' % self._do_id) self.cleanup(False, self._context, field_id, self._field_data) return field_packer.begin_unpack(field) field_args = field.unpack_args(field_packer) field_packer.end_unpack() if not field_args: self.notify.error( 'Failed to unpack field args for field: %d dclass: %s, ' 'invalid result!' % (field.get_name(), dc_class.get_name())) fields[field.get_name()] = field_args file_object.set_value('fields', fields) self.network.backend.close_file(file_object) self.cleanup(True, 0, None) def exitStart(self): pass
class NetworkDCLoader(object): notify = notify.new_category('NetworkDCLoader') def __init__(self): self._dc_file = DCFile() self._dc_suffix = '' self._dclasses_by_name = {} self._dclasses_by_number = {} self._hash_value = 0 @property def dc_file(self): return self._dc_file @property def dc_suffix(self): return self._dc_suffix @property def dclasses_by_name(self): return self._dclasses_by_name @property def dclasses_by_number(self): return self._dclasses_by_number @property def hash_value(self): return self._hash_value def read_dc_files(self, dc_file_names=None): dc_imports = {} if dc_file_names == None: read_result = self._dc_file.read_all() if not read_result: self.notify.error('Could not read dc file.') else: for dc_fileName in dc_file_names: pathname = Filename(dc_fileName) read_result = self._dc_file.read(pathname) if not read_result: self.notify.error('Could not read dc file: %s' % pathname) self._hash_value = self._dc_file.get_hash() # Now get the class definition for the classes named in the DC # file. for i in range(self._dc_file.get_num_classes()): dclass = self._dc_file.get_class(i) number = dclass.get_number() class_name = dclass.get_name() + self._dc_suffix # Does the class have a definition defined in the newly # imported namespace? class_def = dc_imports.get(class_name) # Also try it without the dc_suffix. if class_def == None: class_name = dclass.get_name() class_def = dc_imports.get(class_name) if class_def == None: self.notify.debug('No class definition for %s.' % class_name) else: if inspect.ismodule(class_def): if not hasattr(class_def, class_name): self.notify.error( 'Module %s does not define class %s.' % (class_name, class_name)) class_def = getattr(class_def, class_name) if not inspect.isclass(class_def): self.notify.error('Symbol %s is not a class name.' % class_name) else: dclass.set_class_def(class_def) self._dclasses_by_name[class_name] = dclass if number >= 0: self._dclasses_by_number[number] = dclass
class ClientOperationManager(object): notify = notify.new_category('ClientOperationManager') def __init__(self, network): self._network = network self._channel2fsm = {} @property def network(self): return self._network @property def channel2fsm(self): return self._channel2fsm def has_fsm(self, channel): if channel in self._channel2fsm: #print('%s, %s' % (self._channel2fsm[channel] != None, self._channel2fsm[channel].__class__.__name__)) return self._channel2fsm[channel] != None return channel in self._channel2fsm def add_fsm(self, channel, fsm): if self.has_fsm(channel): self.notify.warning( 'Cannot add FSM for channel %s, ' 'FSM %s is already running!' % (channel, self._channel2fsm[channel].__class__.__name__)) return self._channel2fsm[channel] = fsm def remove_fsm(self, channel): if not self.has_fsm(channel): self.notify.warning( 'Cannot cancel FSM for channel %s, no FSM running!' % channel) return del self._channel2fsm[channel] self._channel2fsm[channel] = None def get_fsm(self, channel): return self._channel2fsm.get(channel) def run_operation(self, fsm, client, callback, *args, **kwargs): if self.has_fsm(client.allocated_channel): self.notify.warning( 'Cannot run operation: %s for channel %d, operation already running: %s!' % (fsm.__name__, client.allocated_channel, self.get_fsm(client.allocated_channel).__class__.__name__)) return None operation = fsm(self, client, callback, *args, **kwargs) self.add_fsm(client.allocated_channel, operation) return operation def stop_operation(self, client): if not self.has_fsm(client.allocated_channel): self.notify.warning('Cannot stop operation for channel %d, ' 'unknown operation!' % client.channel) return operation = self.get_fsm(client.allocated_channel) operation.demand('Off') self.remove_fsm(client.allocated_channel)
class GetAvatarDetailsFSM(ClientOperation): notify = notify.new_category('GetAvatarDetailsFSM') def __init__(self, manager, client, callback, avatar_id): ClientOperation.__init__(self, manager, client, callback) self._avatar_id = avatar_id self._callback = callback self._dc_class = None self._fields = {} def enterStart(self): def response(dclass, fields): if not dclass: self.cleanup(False) return self._dc_class = dclass self._fields = fields self.request('SendDetails') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, response, self.manager.network.dc_loader.dclasses_by_name['DistributedToon']) def exitStart(self): pass def enterSendDetails(self): datagram = io.NetworkDatagram() datagram.add_uint64(self._avatar_id) datagram.add_uint64(0) datagram.add_uint32(0) datagram.add_uint16(0) sorted_fields = {} for field_name, field_args in self._fields.items(): field = self._dc_class.get_field_by_name(field_name) if not field: self.notify.warning('Failed to pack fields for object %d, ' 'unknown field: %s!' % (self._avatar_id, field_name)) self.cleanup(False) return sorted_fields[field.get_number()] = field_args sorted_fields = collections.OrderedDict(sorted(sorted_fields.items())) field_packer = DCPacker() for field_index, field_args in sorted_fields.items(): field = self._dc_class.get_field_by_index(field_index) if not field: self.notify.warning( 'Failed to pack required field: %d for object %d, ' 'unknown field!' % (field_index, self._avatar_id)) self.cleanup(False) return field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() datagram.append_data(field_packer.get_string()) di = io.NetworkDatagramIterator(datagram) # We're all done self.cleanup(True, False, di) def exitSendDetails(self): pass
class SetNamePatternFSM(ClientOperation): notify = notify.new_category('SetNamePatternFSM') def __init__(self, manager, client, callback, avatar_id, pattern): self.notify.debug("SetNamePatternFSM.__init__(%s, %s, %s, %s, %s)" % (str(manager), str(client), str(callback), str(avatar_id), str(pattern))) ClientOperation.__init__(self, manager, client, callback) self._avatar_id = avatar_id self._pattern = pattern self._callback = callback self._dc_class = None self._fields = {} def enterStart(self): self.notify.debug("SetNamePatternFSM.enterQuery()") def response(dclass, fields): self.notify.debug("SetNamePatternFSM.enterQuery.response(%s, %s)" % (str(dclass), str(fields))) if not dclass: self.cleanup(False) return self._dc_class = dclass self._fields = fields self.request('SetPatternName') self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, response, self.manager.network.dc_loader.dclasses_by_name['DistributedToon']) def exitStart(self): self.notify.debug("SetNamePatternFSM.exitQuery()") def enterSetPatternName(self): self.notify.debug("SetNamePatternFSM.enterSetPatternName()") nameGenerator = NameGenerator() # Render the pattern into a string: parts = [] for p, f in self._pattern: part = nameGenerator.nameDictionary.get(p, ('', ''))[1] if f: part = part[:1].upper() + part[1:] else: part = part.lower() parts.append(part) parts[2] += parts.pop( 3) # Merge 2&3 (the last name) as there should be no space. while '' in parts: parts.remove('') name = ' '.join(parts) del nameGenerator new_fields = {'setName': (name, )} #self.notify.warning("New fields are \n%s" % (str(self._fields))) self.manager.network.database_interface.update_object( self.client.channel, types.DATABASE_CHANNEL, self._avatar_id, self.manager.network.dc_loader.dclasses_by_name['DistributedToon'], new_fields) # We're all done self.cleanup(True, self._avatar_id) def exitSetPatternName(self): self.notify.debug("SetNamePatternFSM.exitSetPatternName()")
class ClientAgent(io.NetworkConnector, io.NetworkListener, component.Component): notify = notify.new_category('ClientAgent') def __init__(self, dc_loader): address = config.GetString('clientagent-address', '0.0.0.0') port = config.GetInt('clientagent-port', 6667) connect_address = config.GetString('clientagent-connect-address', '127.0.0.1') connect_port = config.GetInt('clientagent-connect-port', 7100) io.NetworkConnector.__init__(self, dc_loader, connect_address, connect_port) io.NetworkListener.__init__(self, address, port, Client) self._channel = config.GetInt('clientagent-channel', types.CLIENTAGENT_CHANNEL) min_channels = config.GetInt('clientagent-min-channels', 1000000000) max_channels = config.GetInt('clientagent-max-channels', 1009999999) self._channel_allocator = UniqueIdAllocator(min_channels, max_channels - 1) self._server_version = config.GetString('clientagent-version', 'no-version') self._server_hash_val = int( config.GetString('clientagent-hash-val', '0')) self._interest_timeout = config.GetFloat( 'clientagent-interest-timeout', 2.5) self._database_interface = util.DatabaseInterface(self) self._account_manager = ClientAccountManager(self) @property def channel_allocator(self): return self._channel_allocator @property def server_version(self): return self._server_version @property def server_hash_val(self): return self._server_hash_val @property def interest_timeout(self): return self._interest_timeout @property def database_interface(self): return self._database_interface @property def account_manager(self): return self._account_manager def setup(self): io.NetworkListener.setup(self) io.NetworkConnector.setup(self) def handle_datagram(self, channel, sender, message_type, di): handler = self.get_handler_from_channel(channel) if not handler: self.notify.debug('Cannot handle message type: %d ' 'for unknown channel: %d!' % (message_type, channel)) return handler.handle_internal_datagram(message_type, sender, di) def shutdown(self): io.NetworkListener.shutdown(self) io.NetworkConnector.shutdown(self)
class DatabaseRetrieveFSM(DatabaseOperationFSM): notify = notify.new_category('DatabaseRetrieveFSM') def __init__(self, *args, **kwargs): self._context = kwargs.pop('context', 0) self._do_id = kwargs.pop('do_id', 0) DatabaseOperationFSM.__init__(self, *args, **kwargs) def enterStart(self): if not self._do_id: self.notify.warning( 'Failed to get fields for object: %d context: %d, ' 'unknown object!' % (self._do_id, self._context)) self.cleanup(False, self._context, 0, 0, None) return file_object = self.network.backend.open_file('%d' % self._do_id) if not file_object: self.notify.warning( 'Failed to get fields for object: %d context: %d, ' 'unknown object!' % (self._do_id, self._context)) self.cleanup(False, self._context, 0, 0, None) return dc_name = file_object.get_value('dclass') self._dc_class = self.network.dc_loader.dclasses_by_name.get(dc_name) if not self._dc_class: self.notify.warning('Failed to query object: %d context: %d, ' 'unknown dclass: %s!' % (self._do_id, self._context, dc_name)) self.cleanup(False, self._context, 0, 0, None) return self._fields = file_object.get_value('fields') if not self._fields: self.notify.warning('Failed to query object: %d context %d, ' 'invalid fields!' % (self._do_id, self._context)) self.cleanup(False, self._context, 0, 0, None) return self.network.backend.close_file(file_object) field_packer = DCPacker() for field_name, field_args in self._fields.items(): field = self._dc_class.get_field_by_name(field_name) if not field: self.notify.warning('Failed to query object %d context: %d, ' 'unknown field: %s' % (do_id, self._context, field_name)) return field_packer.raw_pack_uint16(field.get_number()) field_packer.begin_pack(field) field.pack_args(field_packer, field_args) field_packer.end_pack() self.cleanup(True, self._context, self._dc_class.get_number(), len(self._fields), field_packer) def exitStart(self): pass
class MessageInterface(object): notify = notify.new_category('MessageInterface') def __init__(self, network): self._network = network self._flush_timeout = config.GetFloat('messagedirector-flush-timeout', 0.001) self._route_usequeue = config.GetBool('messagedirector-route-usequeue', False) self._messages = collections.deque() self._post_messages = {} @property def messages(self): return self._messages @property def post_messages(self): return self._post_messages def append_handle(self, channel, sender, message_type, datagram): if not channel: return #if not datagram.get_length(): # self.notify.warning('Failed to append messenger handle from sender: ' # '%d to channel: %d, invalid datagram!' % (sender, channel)) # # return message_handle = MessageHandle(channel, sender, message_type, datagram) if not self._route_usequeue: self.route_datagram_noqueue(message_handle) return self._messages.append(message_handle) def remove_handle(self, message_handle): if not isinstance(message_handle, MessageHandle): raise MessageError('Failed to remove message handle of ' 'invalid type: %r!' % message_handle) self._messages.remove(message_handle) def append_post_handle(self, channel, datagram): message_handle = PostMessageHandle(channel, datagram) messages = self._post_messages.setdefault(channel, collections.deque()) messages.append(message_handle) def remove_post_handle(self, message_handle): if not isinstance(message_handle, PostMessageHandle): raise MessageError('Failed to remove post message handle of ' 'invalid type: %r!' % message_handle) messages = self._post_messages.get(message_handle.channel) if not messages: self.notify.debug('Failed to remove post message handle, ' 'unknown channel: %d!' % channel) return messages.remove(message_handle) def clear_post_handles(self, channel): messages = self._post_messages.get(channel) if not messages: self.notify.debug('Failed to flush post message handles, ' 'unknown channel: %d!' % channel) return del self._post_messages[channel] def setup(self): self.__flush_task = task_mgr.doMethodLater( self._flush_timeout, self.__flush, self._network.get_unique_name('flush-queue')) def route_datagram(self, message_handle, participant): assert (message_handle != None) datagram = io.NetworkDatagram() datagram.add_header(message_handle.channel, message_handle.sender, message_handle.message_type) other_datagram = message_handle.datagram datagram.append_data(other_datagram.get_message()) participant.handle_send_datagram(datagram) # destroy the datagram and message handle objects since they are # no longer needed in this scope... other_datagram.clear() datagram.clear() del other_datagram del datagram message_handle.destroy() del message_handle def route_datagram_noqueue(self, message_handle): participant = self._network.interface.get_participant( message_handle.channel) if not participant: return self.route_datagram(message_handle, participant) def __flush(self, task): for _ in xrange(len(self._messages)): # pull a message handle object off the top of the queue, # then attempt to route it to its appropiate channel... message_handle = self._messages.popleft() assert (message_handle != None) participant = self._network.interface.get_participant( message_handle.channel) if not participant: #self.notify.warning('Cannot flush message for unknown channel: %d!' % message_handle.channel) continue self.route_datagram(message_handle, participant) return task.again def flush_post_handles(self, channel): messages = self._post_messages.get(channel) if not messages: self.notify.debug('Failed to flush post message handles, ' 'unknown channel: %d!' % channel) return participant = self._network.interface.get_participant(channel) if not participant: self.notify.warning('Failed to flush post message handles, ' 'unknown participant with channel: %d!' % channel) return for _ in xrange(len(messages)): message_handle = messages.popleft() # in order for us to properly handle post remove messages, # we need to unpack and process them like we would normally... datagram = message_handle.datagram participant.handle_datagram(io.NetworkDatagramIterator(datagram)) # destroy the datagram and message handle objects since they are # no longer needed in this scope... datagram.clear() del datagram message_handle.destroy() del message_handle # finally clear our channel from the post removes # dictionary which held the message handle objects... self.clear_post_handles(channel) def flush_all_post_handles(self): for channel in list(self._post_messages.keys()): self.flush_post_handles(channel) def shutdown(self): if self.__flush_task: task_mgr.remove(self.__flush_task) self.__flush_task = None
class DatabaseServer(io.NetworkConnector, component.Component): notify = notify.new_category('DatabaseServer') def __init__(self, dc_loader): connect_address = config.GetString('database-connect-address', '127.0.0.1') connect_port = config.GetInt('database-connect-port', 7100) io.NetworkConnector.__init__(self, dc_loader, connect_address, connect_port) self._channel = config.GetInt('database-channel', types.DATABASE_CHANNEL) self._backend = DatabaseJSONBackend() self._operation_manager = DatabaseOperationManager() @property def backend(self): return self._backend @property def operation_manager(self): return self._operation_manager def setup(self): io.NetworkConnector.setup(self) self._backend.setup() self._operation_manager.setup() def handle_datagram(self, channel, sender, message_type, di): if message_type == types.DBSERVER_CREATE_OBJECT: self.handle_create_object(sender, di) elif message_type == types.DBSERVER_OBJECT_GET_ALL: self.handle_object_get_all(sender, di) elif message_type == types.DBSERVER_OBJECT_SET_FIELD: self.handle_object_set_field(sender, di) elif message_type == types.DBSERVER_OBJECT_SET_FIELD_IF_EQUALS: self.handle_object_set_field_if_equals(sender, di) else: self.notify.warning('Received unknown message type: %d' % message_type) def handle_create_object(self, sender, di): self._operation_manager.add_operation( DatabaseCreateFSM, self, sender, context=di.get_uint32(), dc_id=di.get_uint16(), field_count=di.get_uint16(), field_data=di.get_remaining_bytes(), callback=self._create_object_callback) def _create_object_callback(self, success, sender, context, do_id): datagram = io.NetworkDatagram() datagram.add_header(sender, self.channel, types.DBSERVER_CREATE_OBJECT_RESP) datagram.add_uint32(context) datagram.add_uint32(do_id) self.handle_send_connection_datagram(datagram) def handle_object_get_all(self, sender, di): self._operation_manager.add_operation( DatabaseRetrieveFSM, self, sender, context=di.get_uint32(), do_id=di.get_uint32(), callback=self._object_get_all_callback) def _object_get_all_callback(self, success, sender, context, dc_id, num_fields, field_packer): datagram = io.NetworkDatagram() datagram.add_header(sender, self.channel, types.DBSERVER_OBJECT_GET_ALL_RESP) datagram.add_uint32(context) datagram.add_uint8(success) if success: datagram.add_uint16(dc_id) datagram.add_uint16(num_fields) assert (field_packer is not None) datagram.append_data(field_packer.get_string()) self.handle_send_connection_datagram(datagram) def handle_object_set_field(self, sender, di): self._operation_manager.add_operation( DatabaseSetFieldFSM, self, sender, do_id=di.get_uint32(), field_data=di.get_remaining_bytes()) def handle_object_set_field_if_equals(self, sender, di): self._operation_manager.add_operation( DatabaseSetFieldFSM, self, sender, do_id=di.get_uint32(), context=di.get_uint32(), field_data=di.get_remaining_bytes(), callback=self._object_set_field_if_equals_callback) def _object_set_field_if_equals_callback(self, success, sender, context, field_id, field_data): datagram = io.NetworkDatagram() datagram.add_header(sender, self.channel, types.DBSERVER_OBJECT_SET_FIELD_IF_EQUALS_RESP) datagram.add_uint32(context) datagram.add_uint8(success) if not success: if field_id is not None: field_packer = DCPacker() field = self._dc_class.get_field_by_index(field_id) assert (field is not None) field_packer.raw_pack_uint16(field_id) field_packer.begin_pack(field) field.pack_args(field_packer, field_data) field_packer.end_pack() datagram.add_uint16(1) datagram.append_data(field_packer.get_string()) else: datagram.add_uint16(0) self.handle_send_connection_datagram(datagram) def shutdown(self): self._backend.shutdown() self._operation_manager.shutdown() io.NetworkConnector.shutdown(self)
class CreateAvatarFSM(ClientOperation): notify = notify.new_category('CreateAvatarFSM') def __init__(self, manager, client, callback, echo_context, account_id, dna_string, index): ClientOperation.__init__(self, manager, client, callback) self._account_id = account_id self._dna_string = dna_string self._callback = callback self._echo_context = echo_context self._index = index def enterStart(self): fields = { 'setDNAString': (self._dna_string, ), 'setPosIndex': (self._index, ), 'setName': ('Toon', ), 'setFriendsList': ([], ) } self.manager.network.database_interface.create_object( self.client.channel, types.DATABASE_CHANNEL, self.manager.network.dc_loader.dclasses_by_name['DistributedToon'], fields=fields, callback=lambda avatar_id: self.__avatar_created( avatar_id, self._index)) def __avatar_created(self, avatar_id, index): if not avatar_id: self.notify.warning('Failed to create avatar with index: ' '%d for account with do_id: %d!' % (index, self._account_id)) self.cleanup(False) return self.manager.network.database_interface.query_object( self.client.channel, types.DATABASE_CHANNEL, self._account_id, lambda dclass, fields: self.__account_loaded( dclass, fields, avatar_id, index), self.manager.network.dc_loader.dclasses_by_name['Account']) def __account_loaded(self, dclass, fields, avatar_id, index): if not dclass: self.cleanup(False) return avatar_list = fields['ACCOUNT_AV_SET'][0] avatar_list[index] = avatar_id new_fields = {'ACCOUNT_AV_SET': (avatar_list, )} self.manager.network.database_interface.update_object( self.client.channel, types.DATABASE_CHANNEL, self._account_id, self.manager.network.dc_loader.dclasses_by_name['Account'], new_fields) # We're all done self.cleanup(True, self._echo_context, avatar_id) def exitStart(self): pass