class WorldServerProtocol(Protocol, CloudBoxProtocolMixin, TickMixin): """ I am a Protocol for the World Server. """ PACKET_LIMIT_NAME = "outgoing" ### Twisted-related functions ### def __init__(self, logger): self.logger = logger self.ready = False # Set to False to prevent new connections self.loops = LoopRegistry() def connectionMade(self): self.factory.instance = self self.gpp = MSGPackPacketProcessor(self, self.factory.handlers, self.transport) self.loops.registerLoop("packets", self.gpp.packetLoop).start(self.getTickInterval("outgoing")) self.logger.info("Connecting to Hub Server...") self.sendHandshake() self.ready = True def dataReceived(self, data): self.gpp.feed(data) self.gpp.parseFirstPacket() def connectionLost(self, reason): self.logger.error("Connection to Hub Server lost: {reason}".format(reason=reason)) @property def remoteServerType(self): return self.factory.remoteServerType
class WorldServerCommServerFactory(ServerFactory, CloudBoxFactoryMixin, TaskTickMixin): """ I listen to World Servers and interact with them, acting as a proxy. """ protocol = WorldServerCommServerProtocol remoteServerType = SERVER_TYPES["WorldServer"] IS_CLIENT = False def __init__(self, parentService): self.parentService = parentService self.worldServers = {} self.logger = logging.getLogger("cloudbox.hub.world.factory") self.loops = LoopRegistry() self.loops.registerLoop("tasks", self.taskLoop) def startFactory(self): self.handlers = self.buildHandlers() self.loops["tasks"].start(self.getTickInterval()) def buildHandlers(self): h = dict(HANDLERS_CLIENT_BASIC.items() + HANDLERS_WORLD_SERVER.items()) h[TYPE_HANDSHAKE] = ("cloudbox.hub.handlers", "HubHandshakePacketHandler") return h def getServerStats(self): """ Gets the current load for each World Server. """ dL = DeferredList([]) for wsID, instance in self.worldServers: def cb(stats): return wsID, stats dL.chainDeferred(instance.sendPacket().addCallback(cb)) return dL def getWorldServerByWorldName(self, worldName): return self.db.runQuery(*World.select(World.worldServerID).where(World.name == worldName).sql()) # TODO Reuse this POS - What was I even thinking? def getOnlineWorldServerByWorldName(self, worldName): def cb(res): finalList = [] for row in res: ws = row[0] # Check if it's online if ws in self.worldServers: finalList.append(ws) return finalList return self.getWorldServerByWorldName(worldName).addCallback(cb) def leaveWorldServer(self, proto): """ Leaves the current world server. """ if not self.worldServers.has_key(proto.wsID): raise KeyError("World server ID does not exist or is detached") self.worldServers[proto.wsID].doLeaveServer(proto)
class WorldServerCommServerProtocol(Protocol, CloudBoxProtocolMixin, WorldServerProtocolMixin, TickMixin): """ The protocol class for the WorldServer communicator factory. """ remoteServerType = SERVER_TYPES["WorldServer"] PACKET_LIMIT_NAME = "outgoing-world" def __init__(self): self.id = None self.logger = logging.getLogger("cloudbox.hub.world.protocol._default") # Will be replaced when we get a proper ID self.loops = LoopRegistry() def connectionMade(self): """ Triggered when connection is established. """ self.gpp = MSGPackPacketProcessor(self, self.factory.handlers, self.transport) self.loops.registerLoop("packets", self.gpp.packetLoop).start(self.getTickInterval("outgoing-world")) def dataReceived(self, data): """ Triggered when data is received. """ # Pass on the data to the GPP # First, add the data we got onto our internal buffer self.gpp.feed(data) # Ask the GPP to decode the data, if possible self.gpp.parseFirstPacket() ### World Server related functions ### def getStats(self): # TODO return { "players": 0 } ### End-client related functions ### def protoDoJoinServer(self, proto, world=None): """ Makes the protocol join the server. """ # Send the basic information over def afterNewPlayer(): d = self.sendStateUpdate(proto.sessionID, proto.player, requireResponse=True) self.logger.info("Sent request for {} to join worldServer {}".format(proto.player["username"], self.id)) return d return self.sendNewPlayer(proto.sessionID).addCallback(noArgs(afterNewPlayer)) def protoDoLeaveServer(self, proto): """
def __init__(self): self.id = None self.logger = logging.getLogger("cloudbox.hub.world.protocol._default") # Will be replaced when we get a proper ID self.loops = LoopRegistry()
def __init__(self, parentService): self.parentService = parentService self.worldServers = {} self.logger = logging.getLogger("cloudbox.hub.world.factory") self.loops = LoopRegistry() self.loops.registerLoop("tasks", self.taskLoop)
def __init__(self, parentService): self.parentService = parentService self.clients = {} self.logger = logging.getLogger("cloudbox.hub.mc.factory") self.loops = LoopRegistry() self.loops.registerLoop("task", self.taskLoop).start(self.getTickInterval())
class MinecraftHubServerFactory(ServerFactory, CloudBoxFactoryMixin, TaskTickMixin): """ I am the Minecraft side of the hub. I handle Minecraft client requests and pass them on to World Servers. """ protocol = MinecraftHubServerProtocol IS_CLIENT = False def __init__(self, parentService): self.parentService = parentService self.clients = {} self.logger = logging.getLogger("cloudbox.hub.mc.factory") self.loops = LoopRegistry() self.loops.registerLoop("task", self.taskLoop).start(self.getTickInterval()) def startFactory(self): self.handlers = self.buildHandlers() def buildHandlers(self): handlers = { TYPE_INITIAL: ("cloudbox.common.minecraft.handlers.classic", "HandshakePacketHandler"), TYPE_KEEPALIVE: ("cloudbox.common.minecraft.handlers.classic", "KeepAlivePacketHandler"), TYPE_LEVELINIT: ("cloudbox.common.minecraft.handlers.classic", "LevelInitPacketHandler"), TYPE_LEVELDATA: ("cloudbox.common.minecraft.handlers.classic", "LevelDataPacketHandler"), TYPE_LEVELFINALIZE: ("cloudbox.common.minecraft.handlers.classic", "LevelFinalizePacketHandler"), TYPE_BLOCKCHANGE: ("cloudbox.common.minecraft.handlers.classic", "BlockChangePacketHandler"), TYPE_BLOCKSET: ("cloudbox.common.minecraft.handlers.classic", "BlockSetPacketHandler"), TYPE_SPAWNPLAYER: ("cloudbox.common.minecraft.handlers.classic", "SpawnPlayerPacketHandler"), TYPE_PLAYERPOS: ("cloudbox.common.minecraft.handlers.classic", "PlayerPosPacketHandler"), TYPE_PLAYERORT: ("cloudbox.common.minecraft.handlers.classic", "PlayerOrtPacketHandler"), TYPE_PLAYERDESPAWN: ("cloudbox.common.minecraft.handlers.classic", "PlayerDespawnPacketHandler"), TYPE_MESSAGE: ("cloudbox.common.minecraft.handlers.classic", "MessagePacketHandler"), TYPE_ERROR: ("cloudbox.common.minecraft.handlers.classic", "ErrorPacketHandler"), TYPE_SETUSERTYPE: ("cloudbox.common.minecraft.handlers.classic", "SetUserTypePacketHandler") } if self.settings["main"]["enable-cpe"]: handlers.update({ TYPE_EXTINFO: ("cloudbox.common.minecraft.handlers.cpe", "ExtInfoPacketHandler"), TYPE_EXTENTRY: ("cloudbox.common.minecraft.handlers.cpe", "ExtInfoPacketHandler") }) return handlers def claimID(self, proto): """ Fetches ID for a client protocol instance. """ for i in range(1, self.settings["main"]["max-clients"] + 1): if i not in self.clients: self.clients[i] = {"username": None, "protocol": proto} # TODO - Hook Call Here return i # Server is full return None def releaseID(self, clientID): del self.clients[clientID] ### World Server related functions ### def getWorldServersAvailability(self): statDict = {} for ws in self.getFactory("WorldServerCommServerFactory").worldServers: statDict[ws.id] = self.getWorldServerAvailability(ws.id) def getWorldServerAvailability(self, wsID): return self.getFactory("WorldServerCommServerFactory").worldServers[wsID].getStats() def relayMCPacketToWorldServer(self, packetID, packetData): pass def assignWorldServer(self, proto, world=None): if not world: world = "default" # TODO Link to settings assert proto.wsID is None, "Tried to reassign already assigned client." def afterAddedNewClient(wsProto): # Confirm the assginment proto.wsID = wsProto.id self.logger.info(wsProto.id) def gotWorldServer(res): hasFailed(res) if not res: raise NoResultsException ws = res[0]["worldServerID"] if ws not in self.getFactory("WorldServerCommServerFactory").worldServers: raise WorldServerLinkNotEstablishedException wsProto = self.getFactory("WorldServerCommServerFactory").worldServers[ws] return wsProto.protoDoJoinServer(proto, world).addCallback(noArgs(afterAddedNewClient), wsProto) return self.db.runQuery( *World.select(World.worldServerID).where(World.name == world).sql() ).addBoth(gotWorldServer) def leaveWorldServer(self, proto): pass def buildUsernameList(self, wsID=None): """ Builds a list of {username: client object} by the client list, or specify a WorldServer ID to filter. @param wsID The world server ID to query. Leave None to query all users. @return The usernames dict, in the form of {username: protocol}. """ theList = {} for cID, cEntry in self.clients.items(): if cEntry["username"]: # Ignore those who are still establishing a connection if wsID: if cEntry["proto"].wsID == wsID: theList[cEntry["username"].lower()] = cEntry["protocol"] else: theList[cEntry["username"].lower()] = cEntry["protocol"] return theList ### DB query methods (to be tidied up and moved back to models) ### def getBans(self, username=None, ip=None): """ Fetches the ban information using the information given - username, IP, or both. @param username The username to query for. @param ip The IP to query for. @return Deferred The deferred object. """ assert not (username is None and ip is None) def afterGetUser(res): hasFailed(res) if not res: # First time user return [] return self.db.runQuery(*Bans.select().where(Bans.type == BAN_TYPES["globalBan"] & Bans.username == res[0]["username"]).sql()).addBoth(afterGetBans, username) def afterGetIP(res): hasFailed(res) if not res: # First time visitor return [] return self.db.runQuery(*Bans.select().where(Bans.type == BAN_TYPES["globalIPBan"] & Bans.recordID == res[0]["id"])).addBoth(afterGetBans, str(ip)) def afterGetBans(res, lookupEntity): hasFailed(res) if not res: return {} elif len(res) > 1: # More than one record... self.logger.warn("Multiple global ban detected for lookup entity {}.", lookupEntity) return res[0] self.logger.debug("getBans for username {}, IP {}".format(username, ip)) if username and ip is None: # We need the user id first return self.db.runQuery(*User.select(User.id).where(User.username == username).sql()).addBoth(afterGetUser) elif ip and username is None: # We need the IP id first if not isinstance(ip, IPAddress): ip = IPAddress(ip) return self.db.runQuery(*UserIP.select(UserIP.id).where(UserIP.ip == int(ip)).sql()).addBoth(afterGetIP) #elif ip and username: # TODO return {} def getUserByUsername(self, username): """ Fetches the user record given an username. @param username The username to query for. @return tuple The user record. """ return self.db.runQuery(*User.select().where(User.username == username).sql()) def getUserByUserID(self, userID): """ Fetches the user record given an user. @param userID The user ID to query for. @return tuple The user record. """ return self.db.runQuery(*User.select().where(User.id == userID).sql())
def __init__(self, logger): self.logger = logger self.ready = False # Set to False to prevent new connections self.loops = LoopRegistry()