Viewing file: client.py (22.15 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- coding: utf-8 -*- import os import protocol import xml.sax.saxutils import gettext
_ = gettext.gettext
class Channel: """ """ def logXML(self, text): pass def logBinary(self, data): pass def connect(self, host, port): pass def send(self, data, isBinary = False): pass def close(self): pass class ChannelFeedback: """ """ def registerIncomingData(self, data): pass
def onSessionEnded(self): pass def closed(self, errno = 0): pass class ClientFeedback: def setBusy(self, isBusy): """Called when the client is busy (unable to take requests)""" pass def onConnected(self): pass
def onDisconnected(self, reason): pass def openChannel(self, feedback): """Open a channel to the GGZ server. 'feedback' is the object to notify data received on this channel. """ pass def getLogin(self): """Called when the login credentials are required. Returns the username and password to log in with. If the password is None then log in as guest. """ return ('test', None) def getPassword(self, username): """Called when a password is required. 'username' is the username the password is required for. """ return None def onMOTD(self, motd): """Called when the message of the day is received. 'motd' is the message received. """ pass def roomAdded(self, room): """Called when a room is added. 'room' the new room. """ pass def roomUpdated(self, room): pass
def roomJoined(self, room): pass
def tableAdded(self, table): pass
def tableUpdated(self, table): pass
def tableRemoved(self, table): pass
def playerAdded(self, player): pass
def playerRemoved(self, player): pass def onChat(self, chatType, sender, text): pass
class Game: def __init__(self): self.id = '' self.name = '' self.version = '' self.protocol_engine = '' self.protocol_version = '' self.nPlayers = 0 self.author = '' self.url = ''
class Room: def __init__(self): self.id = '' self.game = None self.nPlayers = 0
class Table: def __init__(self, nSeats): self.id = '' self.room = '' self.game = None self.status = '' self.description = '' self.seats = [] for i in xrange(nSeats): seat = Seat() self.seats.append(seat)
class Seat: def __init__(self): self.type = '' self.user = ''
class Player: def __init__(self): self.name = '' self.type = '' self.table = None self.perms = 0 self.lag = 0 self.room = None self.lastRoom = None class MainChannel(ChannelFeedback, protocol.ParserFeedback):
def __init__(self, client): self.client = client self.decoder = protocol.Decoder(self)
def registerIncomingData(self, data): self.controller.logXML(data) while len(data) > 0: data = self.decoder.feed(data)
def send(self, data, isBinary = False): self.controller.send(data, isBinary)
def onConnected(self): assert(self.client.state is self.client.STATE_DISCONNECTED) self.client.feedback.onConnected() self.client.setState(self.client.STATE_START_SESSION) try: language = os.environ['LANG'] except KeyError: language = 'C'
self.send("<?xml version='1.0' encoding='UTF-8'?>\n") self.send("<SESSION>\n") self.send("<LANGUAGE>%s</LANGUAGE>\n" % xml.sax.saxutils.escape(language)) (self.client.username, self.client.password) = self.client.feedback.getLogin() if self.client.password is None: self.client._loginGuest(self.client.username) else: self.client._login(self.client.username, self.client.password) def onResult(self, action, code): if action == 'login': if code == 'ok': self.client.setState(self.client.STATE_LIST_GAMES) else: if code == 'usr lookup': # Translators: GGZ disconnection error when the supplied password is incorrect self.client.close(_('Incorrect password')) #FIXME: Prompt for a password #self.client.setState(self.client.STATE_GET_PASSWORD)
elif code == 'already logged in': # Translators: GGZ disconnection error when the selected account is already in use self.client.close(_('Account in use')) #FIXME: If guest then prompt for a new user or mangle username until one can be found elif code == 'wrong login type': self.client.setState(self.client.STATE_GET_PASSWORD) else: self.client.close(code)
elif action == 'enter': if code != 'ok': print 'Failed to enter room' self.client.setState(self.client.STATE_READY) return if self.client.room is not None: self.client.room.nPlayers -= 1 self.client.feedback.roomUpdated(self.client.room) room = self.client.enteringRoom
self.client.room = room self.client.players = {} self.client.tables = {} room.nPlayers = 0 self.client.setState(self.client.STATE_LIST_TABLES)
elif action == 'list': if self.client.state is self.client.STATE_LIST_GAMES: self.client.setState(self.client.STATE_LIST_ROOMS) elif self.client.state is self.client.STATE_LIST_ROOMS: for room in self.client.rooms.itervalues(): if room.game is None: break # FIXME: Check have valid room, otherwise go to room 0 self.client.setState(self.client.STATE_ENTERING_ROOM) self.client.enteringRoom = room self.send("<ENTER ROOM='%s'/>\n" % room.id) elif self.client.state is self.client.STATE_LIST_TABLES: self.client.setState(self.client.STATE_LIST_PLAYERS) elif self.client.state is self.client.STATE_LIST_PLAYERS: self.client.setState(self.client.STATE_READY) self.client.feedback.roomEntered(self.client.enteringRoom)
elif action == 'chat': pass# FIXME: could be AT_TABLE self.client.setState(self.client.STATE_READY)
elif action == 'launch': self.client.setState(self.client.STATE_READY) elif action == 'join': if code == 'ok': self.client.setState(self.client.STATE_AT_TABLE) elif code == 'table full': print 'Failed to join table: table full' self.client.setState(self.client.STATE_READY) else: print 'Unknown join result: %s' % action elif action == 'leave': if code == 'ok': self.client.setState(self.client.STATE_READY) pass # TODO else: print 'Unknown leave result: %s' % action
else: print 'Unknown result: %s %s' % (action, code)
def onMOTD(self, motd): self.client.feedback.onMOTD(motd)
def onChat(self, chatType, sender, text): self.client.feedback.onChat(chatType, sender, text) def onJoin(self, tableId, isSpectator): try: table = self._getTable(tableId) except KeyError: print "Unknown JOIN with TABLE='%s'" % tableId return self.client.setState(self.client.STATE_AT_TABLE) g = self.client.feedback.onJoin(table, isSpectator, self.client.channel) self.client.channel.setGame(g) def onLeave(self, reason): self.client.feedback.onLeave(reason) def gameAdded(self, gameId, name, version, author, url, numPlayers, protocol_engine, protocol_version): game = Game() game.id = gameId game.name = name game.version = version game.author = author game.url = url game.numPlayers = numPlayers # FIXME: Make min/max (e.g. can be '1..3') game.protocol_engine = protocol_engine game.protocol_version = protocol_version self.client.games[gameId] = game
def roomAdded(self, roomId, gameId, name, description, nPlayers): try: game = self._getGame(gameId, optional = True) except KeyError: print "Unknown ROOM ADD with GAME='%s'" % gameId return room = Room() room.id = roomId room.game = game room.name = name room.description = description room.nPlayers = int(nPlayers) self.client.rooms[roomId] = room self.client.feedback.roomAdded(room)
def roomPlayersUpdate(self, roomId, nPlayers): try: room = self._getRoom(roomId) except KeyError: print "Unknown ROOM PLAYER UPDATE with ROOM='%s'" % roomId return room.nPlayers = int(nPlayers) self.client.feedback.roomUpdated(room)
def tableAdded(self, roomId, tableId, gameId, status, nSeats, description): try: room = self._getRoom(roomId) game = self._getGame(gameId) except KeyError: print "Unknown TABLE ADD with ROOM='%s' GAME='%s'" % (roomId, gameId) return table = Table(int(nSeats)) table.id = tableId table.room = room table.game = game table.status = status table.description = description self.client.tables[tableId] = table self.client.feedback.tableAdded(table)
def tableStatusChanged(self, tableId, status): try: table = self._getTable(tableId) except KeyError: print "Unknown TABLE STATUS with TABLE='%s'" % tableId return table.status = status self.client.feedback.tableUpdated(table)
def seatChanged(self, roomId, tableId, seatId, seatType, user): try: table = self._getTable(tableId) except KeyError: print "Unknown SEAT CHANGE with TABLE='%s'" % tableId return if table.room.id != roomId: return seat = table.seats[int(seatId)] seat.type = seatType seat.user = user self.client.feedback.tableUpdated(table)
def tableRemoved(self, tableId): try: table = self.client.tables.pop(tableId) except KeyError: print "Unknown TABLE REMOVE with TABLE='%s'" % tableId # We do not know of this table - this could occur if we receive a # table remove event before we get the table list. return self.client.feedback.tableRemoved(table) def onPlayerList(self, roomId, players): try: room = self._getRoom(roomId) for p in players: _ = self._getTable(p.tableId, optional = True) except KeyError: print "Unknown PLAYER LIST with ROOM='%s'" % roomId return self.client.players = {} for p in players: player = Player() player.name = p.name player.type = p.type player.table = self._getTable(p.tableId, optional = True) player.perms = p.perms player.lag = p.lag player.room = room self.client.players[player.name] = player
room.nPlayers = len(players) self.client.feedback.roomUpdated(room)
def playerAdded(self, name, playerType, tableId, perms, lag, roomId, fromRoomId): try: room = self._getRoom(roomId, optional = True) lastRoom = self.client.rooms[fromRoomId] table = self._getTable(tableId, optional = True) except KeyError: print "Unknown PLAYER ADD with ROOM='%s' LASTROOM='%s' TABLE='%s'" % (roomId, fromRoomId, tableId) return player = Player() player.name = name player.type = playerType player.table = table player.perms = perms player.lag = lag player.room = room player.lastRoom = lastRoom self.client.players[player.name] = player
if player.lastRoom is not None: player.lastRoom.nPlayers -= 1 self.client.feedback.roomUpdated(player.lastRoom) if player.room is not None: player.room.nPlayers += 1 self.client.feedback.roomUpdated(player.room) self.client.feedback.playerAdded(player)
def playerRemoved(self, name, roomId, toRoomId): try: player = self.client.players.pop(name) room = self._getRoom(toRoomId, optional = True) except KeyError: print "Unknown PLAYER REMOVE with NAME='%s' ROOM='%s' TOROOM='%s'" % (name, roomId, toRoomId) # We do not know of this player - this could occur if we receive a # player remove event before we get the player list. return player.lastRoom = player.room player.room = room if player.room is not None: player.room.nPlayers += 1 self.client.feedback.roomUpdated(player.room) if player.lastRoom is not None: player.lastRoom.nPlayers -= 1 self.client.feedback.roomUpdated(player.lastRoom) self.client.feedback.playerRemoved(player)
def closed(self, errno = 0): # Translators: GGZ disconnection error when the network link has broken. %s is the system provided error self.client.close(_('Connection closed: %s') % os.strerror(errno)) def _getGame(self, gameId, optional = False): if optional and gameId == '-1': return None return self.client.games[gameId]
def _getRoom(self, roomId, optional = False): if optional and roomId == '-1': return None return self.client.rooms[roomId] def _getTable(self, tableId, optional = False): if optional and tableId == '-1': return None return self.client.tables[tableId] class GameChannel(ChannelFeedback, protocol.ParserFeedback): def __init__(self, client, command): self.client = client self.command = command self.inSession = True self.decoder = protocol.Decoder(self) self.buffer = '' self.game = None def setGame(self, game): self.game = game if len(self.buffer) > 0: self.game.registerIncomingData(self.buffer) self.buffer = ''
def registerIncomingData(self, data): while self.inSession and len(data) > 0: remainder = self.decoder.feed(data) self.controller.logXML(data[:len(data) - len(remainder)]) data = remainder
if len(data) == 0: return self.controller.logBinary(data)
if self.game is None: self.buffer += data else: self.game.registerIncomingData(data)
def onDisconnected(self, reason): print 'Disconnected: %s' % reason
def send(self, data, isBinary = False): self.controller.send(data, isBinary)
def onConnected(self): self.send("<?xml version='1.0' encoding='UTF-8'?>\n") self.send("<SESSION>\n") self.send("<LANGUAGE>en_NZ.UTF-8</LANGUAGE>\n") self.send("<CHANNEL ID='%s' /></SESSION>\n" % self.client.username) def onSessionEnded(self): self.inSession = False self.client.mainChannel.send(self.command)
def closed(self, errno = 0): print 'SEVERE: GGZ channel closed'
class Client: STATE_DISCONNECTED = 'DISCONNECTED' STATE_START_SESSION = 'START_SESSION' STATE_LOGIN = 'LOGIN' STATE_GET_PASSWORD = 'GET_PASSWORD' STATE_LIST_GAMES = 'LIST_GAMES' STATE_LIST_ROOMS = 'LIST_ROOMS' STATE_READY = 'READY' STATE_JOIN_TABLE_CHANNEL = 'JOIN_TABLE_CHANNEL' STATE_JOIN_TABLE = 'JOIN_TABLE' STATE_START_TABLE_CHANNEL = 'START_TABLE_CHANNEL' STATE_START_TABLE = 'START_TABLE' STATE_AT_TABLE = 'AT_TABLE' STATE_LEAVE_TABLE = 'LEAVE_TABLE' STATE_ENTERING_ROOM = 'ENTERING_ROOM' STATE_LIST_TABLES = 'LIST_TABLES' STATE_LIST_PLAYERS = 'LIST_PLAYERS'
def __init__(self, feedback): self.feedback = feedback self.commands = [] self.sending = False self.games = {} self.rooms = {} self.tables = {} self.players = {} self.room = None self.state = self.STATE_DISCONNECTED def isReady(self): """Check if ready for new requests. Returns True if can make a new request. """ return self.state is self.STATE_READY
def close(self, error): self.disconnectionError = error self.setState(self.STATE_DISCONNECTED) def isBusy(self): return not (self.state is self.STATE_READY or self.state is self.STATE_AT_TABLE or self.state is self.STATE_DISCONNECTED)
def setState(self, state): print 'Changing state from %s to %s' % (self.state, state) self.state = state
self.feedback.setBusy(self.isBusy()) if state is self.STATE_LIST_GAMES: self.mainChannel.send("<LIST TYPE='game' FULL='true'/>\n") elif state is self.STATE_LIST_ROOMS: self.mainChannel.send("<LIST TYPE='room' FULL='true'/>\n") elif state is self.STATE_LIST_TABLES: self.mainChannel.send("<LIST TYPE='table'/>\n") elif state is self.STATE_LIST_PLAYERS: self.mainChannel.send("<LIST TYPE='player'/>\n") elif state is self.STATE_GET_PASSWORD: self.feedback.getPassword(self.username) elif state is self.STATE_DISCONNECTED: self.feedback.onDisconnected(self.disconnectionError) self.mainChannel.controller.close()
def start(self): assert(self.state is self.STATE_DISCONNECTED) self.mainChannel = MainChannel(self) self.mainChannel.controller = self.feedback.openChannel(self.mainChannel) def setPassword(self, password): assert(self.state is self.STATE_GET_PASSWORD) if password is None: # Translators: GGZ disconnection error when a password was required for the selected account self.close(_('A password is required')) else: self._login(self.username, password) def _login(self, username, password): assert(self.state is self.STATE_START_SESSION or self.state is self.STATE_GET_PASSWORD) self.setState(self.STATE_LOGIN) username = xml.sax.saxutils.escape(username) password = xml.sax.saxutils.escape(password) self.mainChannel.send("<LOGIN TYPE='normal'><NAME>%s</NAME><PASSWORD>%s</PASSWORD></LOGIN>\n" % (username, password)); def _loginGuest(self, username): assert(self.state is self.STATE_START_SESSION) self.setState(self.STATE_LOGIN) username = xml.sax.saxutils.escape(username) self.mainChannel.send("<LOGIN TYPE='guest'><NAME>%s</NAME></LOGIN>\n" % username); def _loginNew(self, username, password, email): assert(self.state is self.STATE_START_SESSION) self.setState(self.STATE_LOGIN) username = xml.sax.saxutils.escape(username) password = xml.sax.saxutils.escape(password) email = xml.sax.saxutils.escape(email) self.mainChannel.send("<LOGIN TYPE='first'><NAME>%s</NAME><PASSWORD>%s</PASSWORD><EMAIL>%s</EMAIL></LOGIN>\n" % (username, password, email)); def enterRoom(self, room): if self.state is self.STATE_AT_TABLE: print 'At table' return else: assert(self.state is self.STATE_READY) self.setState(self.STATE_ENTERING_ROOM) self.enteringRoom = room self.mainChannel.send("<ENTER ROOM='%s'/>\n" % room.id) def startTable(self, gameId, description, player): if self.state is not self.STATE_READY: print 'Unable to start table' return self.setState(self.STATE_START_TABLE) # Seat types are 'open', 'bot' or 'reserved' or 'player' (latter two have player name in them) command = "<LAUNCH>\n" command += "<TABLE GAME='%s' SEATS='2'>\n" % gameId command += "<DESC>%s</DESC>\n" % xml.sax.saxutils.escape(description) command += "<SEAT NUM='0' TYPE='reserved'>%s</SEAT>\n" % player command += "<SEAT NUM='1' TYPE='open'/>\n" command += "</TABLE>\n" command += "</LAUNCH>\n" self.channel = GameChannel(self, command) self.channel.controller = self.feedback.openChannel(self.channel) def joinTable(self, table): if self.state is self.STATE_AT_TABLE: print 'Already at table' return assert(self.state is self.STATE_READY) self.setState(self.STATE_JOIN_TABLE) command = "<JOIN TABLE='%s' SPECTATOR='false'/>\n" % table.id self.channel = GameChannel(self, command) self.channel.controller = self.feedback.openChannel(self.channel)
def leaveTable(self): assert(self.state is self.STATE_AT_TABLE) self.setState(self.STATE_LEAVE_TABLE) self.mainChannel.send("<LEAVE FORCE='true'/>\n") def sendChat(self, text): self.mainChannel.send("<CHAT TYPE='normal'>%s</CHAT>\n" % xml.sax.saxutils.escape(text))
|