Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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):
        """
Ejemplo n.º 4
0
 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()
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
 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())
Ejemplo n.º 7
0
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())
Ejemplo n.º 8
0
 def __init__(self, logger):
     self.logger = logger
     self.ready = False  # Set to False to prevent new connections
     self.loops = LoopRegistry()