plugin room game: improved docstrings, added '_' as prefix for internal methods names

This commit is contained in:
souliane 2014-01-09 10:28:25 +01:00
commit 9f9ea3acf0
2 changed files with 143 additions and 80 deletions

View file

@ -87,7 +87,7 @@ class Radiocol(object):
def radiocolSongAdded(self, referee, song_path, profile):
"""This method is called by libervia when a song has been uploaded
@param room_jid_param: jid of the room
@param referee: JID of the referee in the room (room userhost + '/' + nick)
@song_path: absolute path of the song added
@param profile_key: %(doc_profile_key)s"""
#XXX: this is a Q&D way for the proof of concept. In the future, the song should

View file

@ -46,7 +46,14 @@ PLUGIN_INFO = {
class RoomGame(object):
"""This class is used to help launching a MUC game."""
"""This class is used to help launching a MUC game.
Bridge methods callbacks: prepareRoom, playerReady, createGame
Triggered methods: userJoinedTrigger, userLeftTrigger
Also called from subclasses: newRound
For examples of messages sequences, please look in sub-classes.
"""
# Values for self.invite_mode (who can invite after the game creation)
FROM_ALL, FROM_NONE, FROM_REFEREE, FROM_PLAYERS = xrange(0, 4)
@ -61,13 +68,28 @@ class RoomGame(object):
REQUEST = '%s/%s[@xmlns="%s"]'
def __init__(self, host):
"""For other plugin to dynamically inherit this class, it is necessary to not use __init__ but _init_.
The subclass itself must be initialized this way:
class MyGame(object):
def inheritFromRoomGame(self, host):
global RoomGame
RoomGame = host.plugins["ROOM-GAME"].__class__
self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {})
def __init__(self, host):
self.inheritFromRoomGame(host)
RoomGame._init_(self, host, ...)
"""
self.host = host
def _init_(self, host, plugin_info, ns_tag, game_init={}, player_init={}):
"""
@param host
@param plugin_info: PLUGIN_INFO map of the game plugin
@ns_tag: couple (nameservice, tag) to construct the messages
@param ns_tag: couple (nameservice, tag) to construct the messages
@param game_init: dictionary for general game initialization
@param player_init: dictionary for player initialization, applicable to each player
"""
@ -89,11 +111,11 @@ class RoomGame(object):
host.trigger.add("MUC user joined", self.userJoinedTrigger)
host.trigger.add("MUC user left", self.userLeftTrigger)
def createOrInvite(self, room, other_players, profile):
def _createOrInvite(self, room, other_players, profile):
"""
This is called only when someone explicitly wants to play.
The game must not be created if one already exists in the room,
or its creation could be postponed until all the expected players
and its creation could be postponed until all the expected players
join the room (in that case it will be created from userJoinedTrigger).
@param room: instance of wokkel.muc.Room
@param other_players: list for other players JID userhosts
@ -102,49 +124,50 @@ class RoomGame(object):
room_jid_s = room.occupantJID.userhost()
nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
nicks = [nick]
if self.gameExists(room_jid_s):
if not self.checkJoinAuth(room_jid_s, user_jid.userhost(), nick):
if self._gameExists(room_jid_s):
if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick):
return
nicks.extend(self.invitePlayers(room, other_players, nick, profile))
self.updatePlayers(room_jid_s, nicks, profile)
nicks.extend(self._invitePlayers(room, other_players, nick, profile))
self._updatePlayers(room_jid_s, nicks, profile)
else:
self.initGame(room_jid_s, nick)
(auth, waiting, missing) = self.checkWaitAuth(room, other_players)
self._initGame(room_jid_s, nick)
(auth, waiting, missing) = self._checkWaitAuth(room, other_players)
nicks.extend(waiting)
nicks.extend(self.invitePlayers(room, missing, nick, profile))
nicks.extend(self._invitePlayers(room, missing, nick, profile))
if auth:
self.createGame(room_jid_s, nicks, profile)
else:
self.updatePlayers(room_jid_s, nicks, profile)
self._updatePlayers(room_jid_s, nicks, profile)
def initGame(self, room_jid_s, referee_nick):
def _initGame(self, room_jid_s, referee_nick):
"""Important: do not add the referee to 'players' yet. For a
<players /> message to be emitted whenever a new player is joining,
it is necessary to not modify 'players' outside of updatePlayers.
it is necessary to not modify 'players' outside of _updatePlayers.
"""
referee = room_jid_s + '/' + referee_nick
self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False}
self.games[room_jid_s].update(copy.deepcopy(self.game_init))
def gameExists(self, room_jid_s, started=False):
def _gameExists(self, room_jid_s, started=False):
"""Return True if a game has been initialized/started.
@param started: if False, the game must be initialized only,
@param started: if False, the game must be initialized to return True,
otherwise it must be initialized and started with createGame.
@return: True if a game is initialized/started in that room"""
return room_jid_s in self.games and (not started or self.games[room_jid_s]['started'])
def checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False):
def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False):
"""Checks if this profile is allowed to join the game.
The parameter nick is used to check if the user is already
a player in that game. When this method is called from
userJoinedTrigger, nick is also used to check the user
identity instead of user_jid_s (see TODO remark below).
identity instead of user_jid_s (see TODO comment below).
@param room_jid_s: the room hosting the game
@param user_jid_s: JID userhost of the user
@param nick: nick of the user
@return: True if this profile can join the game
"""
auth = False
if not self.gameExists(room_jid_s):
if not self._gameExists(room_jid_s):
auth = False
elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick):
auth = True
@ -166,7 +189,7 @@ class RoomGame(object):
debug(_("%s not allowed to join the game %s in %s") % (user_jid_s or nick, self.name, room_jid_s))
return auth
def updatePlayers(self, room_jid_s, nicks, profile):
def _updatePlayers(self, room_jid_s, nicks, profile):
"""Signal to the room that some players joined the game"""
if nicks == []:
return
@ -174,21 +197,21 @@ class RoomGame(object):
if len(new_nicks) == 0:
return
self.games[room_jid_s]['players'].extend(new_nicks)
self.signalPlayers(room_jid_s, [JID(room_jid_s)], profile)
self._signalPlayers(room_jid_s, [JID(room_jid_s)], profile)
def signalPlayers(self, room_jid_s, recipients, profile):
def _signalPlayers(self, room_jid_s, recipients, profile):
"""Let these guys know that we are playing (they may not play themselves)."""
if self.gameExists(room_jid_s, started=True):
element = self.createStartElement(self.games[room_jid_s]['players'])
if self._gameExists(room_jid_s, started=True):
element = self._createStartElement(self.games[room_jid_s]['players'])
else:
element = self.createStartElement(self.games[room_jid_s]['players'], name="players")
element = self._createStartElement(self.games[room_jid_s]['players'], name="players")
elements = [(element, None, None)]
for child in self.getSyncData(room_jid_s):
# TODO: sync data may be different and private to each player,
# in that case send a separate message to the new players
elements.append((child, None, None))
for recipient in recipients:
self.sendElements(recipient, elements, profile=profile)
self._sendElements(recipient, elements, profile=profile)
def getSyncData(self, room_jid_s):
"""This method may be overwritten by any child class.
@ -196,7 +219,7 @@ class RoomGame(object):
"""
return []
def invitePlayers(self, room, other_players, nick, profile):
def _invitePlayers(self, room, other_players, nick, profile):
"""Invite players to a room, associated game may exist or not.
@param room: wokkel.muc.Room instance
@param other_players: list of JID userhosts to invite
@ -205,7 +228,7 @@ class RoomGame(object):
"""
room_jid = room.occupantJID.userhostJID()
room_jid_s = room.occupantJID.userhost()
if not self.checkInviteAuth(room_jid_s, nick):
if not self._checkInviteAuth(room_jid_s, nick):
return []
self.invitations.setdefault(room_jid_s, [])
# TODO: remove invitation waiting for too long, using the time data
@ -220,13 +243,18 @@ class RoomGame(object):
nicks.append(other_nick)
return nicks
def checkInviteAuth(self, room_jid_s, nick, verbose=False):
"""Checks if this profile is allowed to invite players"""
def _checkInviteAuth(self, room_jid_s, nick, verbose=False):
"""Checks if this user is allowed to invite players
@param room_jid_s: room userhost
@param nick: user nick in the room
@param verbose: display debug message
@return: True if the user is allowed to invite other players
"""
auth = False
if self.invite_mode == self.FROM_ALL or not self.gameExists(room_jid_s):
if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s):
auth = True
elif self.invite_mode == self.FROM_NONE:
auth = not self.gameExists(room_jid_s, started=True)
auth = not self._gameExists(room_jid_s, started=True)
elif self.invite_mode == self.FROM_REFEREE:
auth = self.isReferee(room_jid_s, nick)
elif self.invite_mode == self.FROM_PLAYERS:
@ -236,22 +264,32 @@ class RoomGame(object):
return auth
def isReferee(self, room_jid_s, nick):
"""Checks if the player with this nick is the referee for the game in this room"""
if not self.gameExists(room_jid_s):
"""Checks if the player with this nick is the referee for the game in this room"
@param room_jid_s: room userhost
@param nick: user nick in the room
@return: True if the user is the referee of the game in this room
"""
if not self._gameExists(room_jid_s):
return False
return room_jid_s + '/' + nick == self.games[room_jid_s]['referee']
def isPlayer(self, room_jid_s, nick):
"""Checks if the player with this nick is a player for the game in this room.
Important: the referee is not in the 'players' list right after the game
initialization - check with isReferee to be sure nick is not a player.
"""Checks if the user with this nick is a player for the game in this room.
@param room_jid_s: room userhost
@param nick: user nick in the room
@return: True if the user is a player of the game in this room
"""
if not self.gameExists(room_jid_s):
if not self._gameExists(room_jid_s):
return False
# Important: the referee is not in the 'players' list right after
# the game initialization, that's why we do also check with isReferee
return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick)
def checkWaitAuth(self, room, other_players, verbose=False):
"""Check if we must wait before starting the game or not.
def _checkWaitAuth(self, room, other_players, verbose=False):
"""Check if we must wait for other players before starting the game.
@param room: wokkel.muc.Room instance
@param other_players: list of the players without the referee
@param verbose: display debug message
@return: (x, y, z) with:
x: False if we must wait, True otherwise
y: the nicks of the players that have been checked and confirmed
@ -270,13 +308,19 @@ class RoomGame(object):
return result
def getUniqueName(self, muc_service="", profile_key='@DEFAULT@'):
"""
@param muc_service: you can leave empty to autofind the muc service
@param profile_key
@return: a unique name for a new room to be created
"""
room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key)
return "sat_%s_%s" % (self.name.lower(), room) if room != "" else ""
def prepareRoom(self, other_players=[], room_jid=None, profile_key='@NONE@'):
"""Prepare the room for a game: create it and invite players.
def prepareRoom(self, other_players=[], room_jid_s=None, profile_key='@NONE@'):
"""Prepare the room for a game: create it if it doesn't exist and invite players.
@param other_players: list for other players JID userhosts
@param room_jid: JID userhost of the room to reuse or None to create a new room
@param room_jid_s: JID userhost of the room, or None to generate a unique name
@param profile_key
"""
debug(_('Preparing room for %s game') % self.name)
profile = self.host.memory.getProfileName(profile_key)
@ -286,29 +330,29 @@ class RoomGame(object):
def roomJoined(room):
"""@param room: instance of wokkel.muc.Room"""
self.createOrInvite(room, other_players, profile)
self._createOrInvite(room, other_players, profile)
def afterClientInit(room_jid):
def afterClientInit(room_jid_s):
"""Create/join the given room, or a unique generated one if no room is specified.
@param room_jid: room to join
@param room_jids: userhost of the room to join
"""
if room_jid is not None and room_jid != "": # a room name has been specified
if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid])
if room_jid_s is not None and room_jid_s != "": # a room name has been specified
if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s])
return
else:
room_jid = self.getUniqueName(profile_key=profile_key)
if room_jid == "":
room_jid_s = self.getUniqueName(profile_key=profile_key)
if room_jid_s == "":
return
user_jid = self.host.getJidNStream(profile)[0]
d = self.host.plugins["XEP-0045"].join(JID(room_jid), user_jid.user, {}, profile)
d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile)
d.addCallback(roomJoined)
client = self.host.getClient(profile)
if not client:
error(_('No client for this profile key: %s') % profile_key)
return
client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid))
client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid_s))
def userJoinedTrigger(self, room, user, profile):
"""This trigger is used to check if the new user can take part of a game,
@ -321,9 +365,9 @@ class RoomGame(object):
profile_nick = room.occupantJID.resource
if not self.isReferee(room_jid_s, profile_nick):
return True # profile is not the referee
if not self.checkJoinAuth(room_jid_s, nick=user.nick):
if not self._checkJoinAuth(room_jid_s, nick=user.nick):
# user not allowed but let him know that we are playing :p
self.signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile)
self._signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile)
return True
if self.wait_mode == self.FOR_ALL:
# considering the last batch of invitations
@ -332,14 +376,14 @@ class RoomGame(object):
error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s))
return True
other_players = self.invitations[room_jid_s][batch][1]
(auth, nicks, dummy) = self.checkWaitAuth(room, other_players)
(auth, nicks, dummy) = self._checkWaitAuth(room, other_players)
if auth:
del self.invitations[room_jid_s][batch]
nicks.insert(0, profile_nick) # add the referee
self.createGame(room_jid_s, nicks, profile_key=profile)
return True
# let the room know that a new player joined
self.updatePlayers(room_jid_s, [user.nick], profile)
self._updatePlayers(room_jid_s, [user.nick], profile)
return True
def userLeftTrigger(self, room, user, profile):
@ -368,27 +412,29 @@ class RoomGame(object):
self.invitations[room_jid_s][batch][1].append(user_jid)
return True
def checkCreateGameAndInit(self, room_jid_s, profile):
def _checkCreateGameAndInit(self, room_jid_s, profile):
"""Check if that profile can create the game. If the game can be created
but is not initialized yet, this method will also do the initialization
but is not initialized yet, this method will also do the initialization.
@param room_jid_s: room userhost
@param profile
@return: a couple (create, sync) with:
- create: set to True to allow the game creation
- sync: set to True to advice a game synchronization
"""
user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
if not user_nick:
error('Internal error')
error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s))
return False, False
if self.gameExists(room_jid_s):
referee = self.isReferee(room_jid_s, user_nick)
if self.gameExists(room_jid_s, started=True):
if self._gameExists(room_jid_s):
is_referee = self.isReferee(room_jid_s, user_nick)
if self._gameExists(room_jid_s, started=True):
warning(_("%s game already created in room %s") % (self.name, room_jid_s))
return False, referee
elif not referee:
return False, is_referee
elif not is_referee:
warning(_("%s game in room %s can only be created by %s") % (self.name, room_jid_s, user_nick))
return False, False
else:
self.initGame(room_jid_s, user_nick)
self._initGame(room_jid_s, user_nick)
return True, False
def createGame(self, room_jid_s, nicks=[], profile_key='@NONE@'):
@ -403,16 +449,16 @@ class RoomGame(object):
if not profile:
error(_("profile %s is unknown") % profile_key)
return
(create, sync) = self.checkCreateGameAndInit(room_jid_s, profile)
(create, sync) = self._checkCreateGameAndInit(room_jid_s, profile)
if not create:
if sync:
debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks)))
# TODO: we should call a method to re-send the information to a player who left
# and joined the room again, currently: we may restart a whole new round...
self.updatePlayers(room_jid_s, nicks, profile)
self._updatePlayers(room_jid_s, nicks, profile)
return
self.games[room_jid_s]['started'] = True
self.updatePlayers(room_jid_s, nicks, profile)
self._updatePlayers(room_jid_s, nicks, profile)
if self.player_init == {}:
return
# specific data to each player
@ -425,16 +471,26 @@ class RoomGame(object):
self.games[room_jid_s].update({'status': status, 'players_data': players_data})
def playerReady(self, player, referee, profile_key='@NONE@'):
"""Must be called when player is ready to start a new game"""
"""Must be called when player is ready to start a new game
@param player: the player nick in the room
@param referee: referee userhost
"""
profile = self.host.memory.getProfileName(profile_key)
if not profile:
error(_("profile %s is unknown") % profile_key)
return
debug('new player ready: %s' % profile)
# TODO: we probably need to add the game and room names in the sent message
self.send(JID(referee), 'player_ready', {'player': player}, profile=profile)
def newRound(self, room_jid, data, profile):
"""Launch a new round (reinit the user data)"""
"""Launch a new round (reinit the user data)
@param room_jid: room userhost
@param data: a couple (common_data, msg_elts) with:
- common_data: backend initialization data for the new round
- msg_elts: dict to map each user to his specific initialization message
@param profile
"""
debug(_('new round for %s game') % self.name)
game_data = self.games[room_jid.userhost()]
players = game_data['players']
@ -454,8 +510,11 @@ class RoomGame(object):
for player in players:
players_data[player].update(common_data)
def createGameElt(self, to_jid, type_="normal"):
"""Create a generic domish Element for the game"""
def _createGameElt(self, to_jid):
"""Create a generic domish Element for the game messages
@param to_jid: JID of the recipient
@return: the created element
"""
type_ = "normal" if to_jid.resource else "groupchat"
elt = domish.Element((None, 'message'))
elt["to"] = to_jid.full()
@ -463,9 +522,13 @@ class RoomGame(object):
elt.addElement(self.ns_tag)
return elt
def createStartElement(self, players=None, name="started"):
"""Create a game "started" domish Element
@param name: element name (default: "started").
def _createStartElement(self, players=None, name="started"):
"""Create a domish Element listing the game users
@param players: list of the players
@param name: element name:
- "started" to signal the players that the game has been started
- "players" to signal the list of players when the game is not started yet
@return the create element
"""
started_elt = domish.Element((None, name))
if players is None:
@ -479,7 +542,7 @@ class RoomGame(object):
started_elt.addChild(player_elt)
return started_elt
def sendElements(self, to_jid, data, profile=None):
def _sendElements(self, to_jid, data, profile=None):
"""
@param to_jid: recipient JID
@param data: list of (elem, attr, content) with:
@ -494,7 +557,7 @@ class RoomGame(object):
if profile is None:
error(_("Message can not be sent without a sender profile"))
return
msg = self.createGameElt(to_jid)
msg = self._createGameElt(to_jid)
for elem, attrs, content in data:
if elem is not None:
if isinstance(elem, domish.Element):
@ -519,7 +582,7 @@ class RoomGame(object):
@param content: unicode that is appended to the child content
@param profile: the profile from which the message is sent
"""
self.sendElements(to_jid, [(elem, attrs, content)], profile)
self._sendElements(to_jid, [(elem, attrs, content)], profile)
def getHandler(self, profile):
return RoomGameHandler(self)