class ServerChannel(object): """ Represents a channel. @ivar id: ID of the channel. @ivar name: The name of the channel. @ivar connections: List of the clients in the channel. @ivar autoClose: True if this channel will close after the master leaves @ivar master: The master connection of this channel @type master: L{ServerProtocol} object """ id = None name = None hidden = False autoClose = False master = None def __init__(self, name, id, hidden, autoClose, master): self.name = name self.id = id self.hidden = hidden self.autoClose = autoClose self.master = master self.connections = MultikeyDict() def addConnection(self, connection): """ Add a client to the channel, and notify the other clients. @param connection: The protocol instance to add. """ toClient = server.Peer() toClient.name = connection.name toClient.peer = connection.id toClient.channel = self.id toClient.isMaster = connection is self.master self.sendLoader(toClient) self.connections[connection.name, connection.id] = connection def removeConnection(self, connection): """ Remove a client from the channel, and notify the other clients. @param connection: The protocol instance to remove. """ # tell the other clients this client is leaving toClient = server.Peer() toClient.peer = connection.id toClient.channel = self.id self.sendLoader(toClient, [connection]) # remove client del self.connections[connection] # close the channel if autoClose is set, and the removed client was # the master if connection is self.master and self.autoClose: close = len(self.connections) > 0 for connection in self.connections.values(): connection.leaveChannel(self) connection.channelLeft(self) return close return len(self.connections) > 0 def sendMessage(self, message, subchannel = 0, fromConnection = None, asObject = False, typeName = None, **settings): """ Send a channel message from the given protocol instance. @param fromConnection: The client that the message is sent from (or None if the message is from the server). @param message: The message to send. @type message: str/number/ByteReader @param subchannel: The subchannel to send the message on. @type subchannel: number @param typeName: If not specified, the type will be automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES} for possible values) """ if typeName is None: typeName = detectType(message) if asObject: if fromConnection is None: newMessage = server.ObjectServerChannelMessage() else: newMessage = server.ObjectChannelMessage() else: if fromConnection is None: newMessage = server.BinaryServerChannelMessage() else: newMessage = server.BinaryChannelMessage() newMessage.channel = self.id newMessage.value = message newMessage.subchannel = subchannel if fromConnection is not None: newMessage.peer = fromConnection.id newMessage.setDataType(typeName) self.sendLoader(newMessage, [fromConnection], **settings) def sendPrivateMessage(self, message, subchannel, sender, recipient, asObject = False, typeName = None, **settings): """ Send a message from this client to a recipient @type message: str/number/ByteReader @param subchannel: The subchannel of the message in the range 0-256 @param sender: Sending connection @type sender: L{ServerProtocol} object @param recipient: Connection to send message to @type recipient: L{ServerProtocol} object @param typeName: If not specified, the type will be automatically detected (see L{lacewing.packetloaders.common.DATA_TYPES} for possible values) """ if typeName is None: typeName = detectType(message) if asObject: newMessage = server.ObjectPeerMessage() else: newMessage = server.BinaryPeerMessage() newMessage.channel = self.id newMessage.value = message newMessage.subchannel = subchannel newMessage.peer = sender.id newMessage.setDataType(typeName) recipient.sendLoader(newMessage, **settings) def sendLoader(self, type, notClients = [], **settings): """ Send the specified type to all the clients in the channel, apart from notClients if specified. """ toClients = [connection for connection in self.connections.values() if connection not in notClients] for client in toClients: client.sendLoader(type, **settings)
class ServerFactory(protocol.ServerFactory): """ The server factory. @ivar channelClass: This is the channel class that will be used when creating a new channel. Subclass L{ServerChannel} and replace this attribute if you want to change the behaviour of channels. @ivar maxPing: This is the time the client has to respond to pings (in seconds). Can be a number or None for no max ping (default) @ivar pingTime: The interval between pings in seconds @ivar maxUsers: The max number of users allowed on the server. @ivar timeOut: The number of seconds the client has to send a Hello packet before being disconnected. Can be a number or None for no timeout @ivar welcomeMessage: The message sent to accepted clients. @ivar ping: If True, pinging will be enabled on the server @ivar channelListing: If True, channelListing is enabled on the server @ivar masterRights: If True, this enables the autoclose feature for clients when creating channels """ channelClass = ServerChannel timeOut = 8 pingTime = 8 maxPing = None maxUsers = 1000 welcomeMessage = 'Welcome! Server is running pylacewing %s (%s)' % ( lacewing.__version__, sys.platform) datagram = None ping = True channelListing = True masterRights = False _pinger = None def startFactory(self): self.connections = MultikeyDict() self.channels = MultikeyDict() self.userPool = IDPool() self.channelPool = IDPool() if self.ping: self._pinger = pinger = LoopingCall(self.globalPing) pinger.start(self.pingTime, False) def globalPing(self): """ Pings all clients currently connected to the server """ for connection in self.connections.values(): connection.ping() def getWelcomeMessage(self, connection): """ This method is called when a connection has been accepted, and a welcome message has to be sent. The default implementation just returns L{welcomeMessage}, but override this method to change that behaviour. @param connection: Connection that has been accepted @type connection: L{ServerProtocol} object @rtype: str """ return self.welcomeMessage def createChannel(self, name, hidden, autoClose, master): if not self.masterRights: autoClose = False try: channel, = self.channels[name] except KeyError: id = self.channelPool.pop() channel = self.channelClass(name, id, hidden, autoClose, master) self.channels[name, id] = channel self.channelAdded(channel) return channel def destroyChannel(self, channel): del self.channels[channel] self.channelRemoved(channel) def channelRemoved(self, channel): """ Called when a channel has no users in it, and is therefore removed. @arg channel: The channel that is being removed. """ def channelAdded(self, channel): """
class Channel(object): name = None id = None factory = None connections = None def __init__(self, name, id, factory): self.name = name self.id = id self.factory = factory self.connections = MultikeyDict() def addConnection(self, connection): self.connections[connection.name, connection.id] = connection connection.channels[self.name, self.id] = self newJoined = ChannelJoined() newJoined.setChannel(self) newJoined.setConnection(connection) connection.sendLoader(newJoined) newExists = PlayerExists() newExists.setChannel(self) newJoined = PlayerJoined() newJoined.setChannel(self) newJoined.setConnection(connection) for otherConnection in self.connections.values(): if otherConnection != connection: newExists.setConnection(otherConnection) connection.sendLoader(newExists) otherConnection.sendLoader(newJoined) def removeConnection(self, connection): newLeft = PlayerLeft() newLeft.setChannel(self) newLeft.setConnection(connection) self.sendLoader(newLeft) del self.connections[connection] del connection.channels[self] return len(self.connections) != 0 def sendMessage(self, connection, value, subchannel, type = None, toClient = None): newValue = Message(**connection.settings) newValue.type = type or detectType(value) newValue.value = value newMessage = FromChannelMessage() newMessage.setChannel(self) newMessage.setConnection(connection) newMessage.message = newValue newMessage.subchannel = subchannel if toClient: toClient.sendLoader(newMessage) else: self.sendLoader(newMessage, connection) def sendLoader(self, loader, fromClient = None): for connection in self.connections.values(): if connection != fromClient: connection.sendLoader(loader) def getMaster(self): return self.connections.values()[0]