diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index bd806f9fd..29b27e479 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1593,7 +1593,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, self.dispatch('FAILED_DECRYPT', (frm, tim)) msgtxt = msg.getBody() - msghtml = msg.getXHTML() subject = msg.getSubject() # if not there, it's None tim = msg.getTimestamp() @@ -1612,19 +1611,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, frm = address.getAttr('jid') jid = gajim.get_jid_without_resource(frm) - encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) # invitations invite = None + encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED) if not encTag: invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER) if invite and not invite.getTag('invite'): invite = None - delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None - msg_id = None - composing_xep = None - # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED # invitation # stanza (MUC XEP) remove in 2007, as we do not do NOT RECOMMENDED @@ -1639,37 +1634,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, is_continued)) return - form_node = None - for xtag in xtags: - if xtag.getNamespace() == common.xmpp.NS_DATA: - form_node = xtag - break - - chatstate = None - - # chatstates - look for chatstate tags in a message if not delayed - if not delayed: - composing_xep = False - children = msg.getChildren() - for child in children: - if child.getNamespace() == 'http://jabber.org/protocol/chatstates': - chatstate = child.getName() - composing_xep = 'XEP-0085' - break - # No XEP-0085 support, fallback to XEP-0022 - if not chatstate: - chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT) - if chatstate_child: - chatstate = 'active' - composing_xep = 'XEP-0022' - if not msgtxt and chatstate_child.getTag('composing'): - chatstate = 'composing' - - # XEP-0172 User Nickname - user_nick = msg.getTagData('nick') - if not user_nick: - user_nick = '' - if encTag and self.USE_GPG: encmsg = encTag.getData() @@ -1683,44 +1647,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, if mtype == 'error': self.dispatch_error_message(msg, msgtxt, session, frm, tim, subject) - - return elif mtype == 'groupchat': - self.dispatch_gc_message(msg, subject, frm, msgtxt, jid, tim, msghtml) - - return + self.dispatch_gc_message(msg, subject, frm, msgtxt, jid, tim) elif invite is not None: self.dispatch_invite_message(invite, frm) - - return - elif mtype == 'chat': - if not msg.getTag('body') and chatstate is None: # no - return - - log_type = 'chat_msg_recv' - else: # it's a single message - log_type = 'single_msg_recv' - - mtype = 'normal' - - if session.is_loggable() and msgtxt: - try: - msg_id = gajim.logger.write(log_type, frm, msgtxt, - tim = tim, subject = subject) - except exceptions.PysqliteOperationalError, e: - self.dispatch('ERROR', (_('Disk Write Error'), str(e))) - - treat_as = gajim.config.get('treat_incoming_messages') - - if treat_as: - mtype = treat_as - - # XXX horrible hack - if isinstance(session, ChatControlSession): - session.received(frm, msgtxt, tim, encrypted, mtype, subject, chatstate, - msg_id, composing_xep, user_nick, msghtml, form_node) else: - session.received(msg) + # XXX horrible hack + if isinstance(session, ChatControlSession): + session.received(frm, msgtxt, tim, encrypted, subject, msg) + else: + session.received(msg) # END messageCB # process and dispatch an error message @@ -1741,7 +1677,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt, tim)) # process and dispatch a groupchat message - def dispatch_gc_message(self, msg, subject, frm, msgtxt, jid, tim, msghtml): + def dispatch_gc_message(self, msg, subject, frm, msgtxt, jid, tim): has_timestamp = bool(msg.timestamp) if subject != None: @@ -1761,7 +1697,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, if not self.last_history_line.has_key(jid): return - self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml, + self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), statusCode)) no_log_for = gajim.config.get_per('accounts', self.name, diff --git a/src/session.py b/src/session.py index 643c7eaf7..ea38ee682 100644 --- a/src/session.py +++ b/src/session.py @@ -1,9 +1,12 @@ from common import helpers +from common import exceptions from common import gajim from common import stanza_session from common import contacts +import common.xmpp + import dialogs import message_control @@ -16,8 +19,70 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): self.control = None + # extracts chatstate from a stanza + def get_chatstate(self, msg, msgtxt): + composing_xep = None + chatstate = None + + # chatstates - look for chatstate tags in a message if not delayed + delayed = msg.getTag('x', namespace=common.xmpp.NS_DELAY) != None + if not delayed: + composing_xep = False + children = msg.getChildren() + for child in children: + if child.getNamespace() == 'http://jabber.org/protocol/chatstates': + chatstate = child.getName() + composing_xep = 'XEP-0085' + break + # No XEP-0085 support, fallback to XEP-0022 + if not chatstate: + chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT) + if chatstate_child: + chatstate = 'active' + composing_xep = 'XEP-0022' + if not msgtxt and chatstate_child.getTag('composing'): + chatstate = 'composing' + + return (composing_xep, chatstate) + # dispatch a received stanza - def received(self, full_jid_with_resource, message, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node): + def received(self, full_jid_with_resource, msgtxt, tim, encrypted, subject, msg): + msg_type = msg.getType() + msg_id = None + + # XEP-0172 User Nickname + user_nick = msg.getTagData('nick') + if not user_nick: + user_nick ='' + + form_node = None + for xtag in msg.getTags('x'): + if xtag.getNamespace() == common.xmpp.NS_DATA: + form_node = xtag + break + + composing_xep, chatstate = self.get_chatstate(msg, msgtxt) + + xhtml = msg.getXHTML() + + if msg_type == 'chat': + if not msg.getTag('body') and chatstate is None: + return + + log_type = 'chat_msg_recv' + else: + log_type = 'single_msg_recv' + + if self.is_loggable() and msgtxt: + try: + msg_id = gajim.logger.write(log_type, full_jid_with_resource, msgtxt, + tim=tim, subject=subject) + except exceptions.PysqliteOperationalError, e: + gajim.dispatch('ERROR', (_('Disk WriteError'), str(e))) + + treat_as = gajim.config.get('treat_incoming_messages') + if treat_as: + msg_type = treat_as jid = gajim.get_jid_without_resource(full_jid_with_resource) resource = gajim.get_resource_from_jid(full_jid_with_resource) @@ -65,7 +130,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): # THIS MUST BE AFTER chatstates handling # AND BEFORE playsound (else we ear sounding on chatstates!) - if not message: # empty message text + if not msgtxt: # empty message text return if gajim.config.get('ignore_unknown_contacts') and \ @@ -86,16 +151,16 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if pm: nickname = resource - groupchat_control.on_private_message(nickname, message, array[2], + groupchat_control.on_private_message(nickname, msgtxt, array[2], xhtml, session, msg_id) else: - self.roster_message(jid, message, tim, encrypted, msg_type, + self.roster_message(jid, msgtxt, tim, encrypted, msg_type, subject, resource, msg_id, user_nick, advanced_notif_num, xhtml=xhtml, form_node=form_node) nickname = gajim.get_name_from_jid(self.conn.name, jid) # Check and do wanted notifications - msg = message + msg = msgtxt if subject: msg = _('Subject: %s') % subject + '\n' + msg focused = False @@ -111,7 +176,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if gajim.interface.remote_ctrl: gajim.interface.remote_ctrl.raise_signal('NewMessage', - (self.conn.name, [full_jid_with_resource, message, tim, + (self.conn.name, [full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject, chatstate, msg_id, composing_xep, user_nick, xhtml, form_node])) diff --git a/src/tictactoe.py b/src/tictactoe.py index 4477b1b10..5bb20ea1b 100644 --- a/src/tictactoe.py +++ b/src/tictactoe.py @@ -9,6 +9,8 @@ import cairo # implements +games_ns = 'http://jabber.org/protocol/games' + class InvalidMove(Exception): pass @@ -24,13 +26,19 @@ class TicTacToeSession(stanza_session.StanzaSession): else: self.role_o = 'x' + self.send_invitation() + + self.next_move_id = 1 + self.received = self.wait_for_invite_response + + def send_invitation(self): msg = xmpp.Message() invite = msg.NT.invite - invite.setNamespace('http://jabber.org/protocol/games') + invite.setNamespace(games_ns) game = invite.NT.game - game.setAttr('var', 'http://jabber.org/protocol/games/tictactoe') + game.setAttr('var', games_ns + '/tictactoe') x = xmpp.DataForm(typ='submit') @@ -38,12 +46,8 @@ class TicTacToeSession(stanza_session.StanzaSession): self.send(msg) - self.next_move_id = 1 - self.state = 'sent_invite' - - # received an invitation - def invited(self, msg): - invite = msg.getTag('invite', namespace='http://jabber.org/protocol/games') + def read_invitation(self, msg): + invite = msg.getTag('invite', namespace=games_ns) game = invite.getTag('game') x = game.getTag('x', namespace='jabber:x:data') @@ -51,37 +55,47 @@ class TicTacToeSession(stanza_session.StanzaSession): if form.getField('role'): self.role_o = form.getField('role').getValues()[0] + else: + self.role_o = 'x' if form.getField('rows'): self.rows = int(form.getField('rows').getValues()[0]) + else: + self.rows = 3 if form.getField('cols'): self.cols = int(form.getField('cols').getValues()[0]) - - # XXX 'strike' - - if not hasattr(self, 'rows'): - self.rows = 3 - - if not hasattr(self, 'cols'): + else: self.cols = 3 + if form.getField('strike'): + self.strike = int(form.getField('strike').getValues()[0]) + else: + self.strike = 3 + + # received an invitation + def invited(self, msg): + self.read_invitation(msg) + + # XXX prompt user + # "accept, reject, ignore" + # the number of the move about to be made self.next_move_id = 1 + # display the board self.board = TicTacToeBoard(self, self.rows, self.cols) # accept the invitation, join the game response = xmpp.Message() join = response.NT.join - join.setNamespace('http://jabber.org/protocol/games') + join.setNamespace(games_ns) self.send(response) - if not hasattr(self, 'role_o') or self.role_o == 'x': + if self.role_o == 'x': self.role_s = 'o' - self.role_o = 'x' self.their_turn() else: @@ -91,27 +105,28 @@ class TicTacToeSession(stanza_session.StanzaSession): self.our_turn() def is_my_turn(self): - return self.state == 'get_input' + # XXX not great semantics + return self.received == self.ignore - def received(self, msg): - # just sent an invitation, expecting a reply - if self.state == 'sent_invite': - if msg.getTag('join', namespace='http://jabber.org/protocol/games'): - self.board = TicTacToeBoard(self, self.rows, self.cols) + # just sent an invitation, expecting a reply + def wait_for_invite_response(self, msg): + if msg.getTag('join', namespace=games_ns): + self.board = TicTacToeBoard(self, self.rows, self.cols) - if self.role_s == 'x': - self.our_turn() - else: - self.their_turn() + if self.role_s == 'x': + self.our_turn() + else: + self.their_turn() - return + elif msg.getTag('decline', namespace=games_ns): + self.XXX() - # ignore messages unless we're expecting a move - if self.state != 'waiting': - return - - turn = msg.getTag('turn', namespace='http://jabber.org/protocol/games') + # silently ignores any received messages + def ignore(self, msg): + pass + def wait_for_move(self, msg): + turn = msg.getTag('turn', namespace=games_ns) move = turn.getTag('move', namespace='http://jabber.org/protocol/games/tictactoe') row = int(move.getAttr('row')) @@ -128,40 +143,54 @@ class TicTacToeSession(stanza_session.StanzaSession): print 'received invalid move' return - # XXX check win conditions + # check win conditions + if self.board.check_for_strike(self.role_o, row, col, self.strike): + self.lost() + elif self.board.full(): + self.drawn() + else: + self.next_move_id += 1 - self.next_move_id += 1 - - self.our_turn() + self.our_turn() def our_turn(self): - self.state = 'get_input' + # ignore messages until we've made our move + self.received = self.ignore self.board.win.set_title(self.board.title + ': your turn') def their_turn(self): - self.state = 'waiting' + self.received = self.wait_for_move self.board.win.set_title(self.board.title + ': their turn') # called when the board receives input - def move(self, row, column): + def move(self, row, col): try: - self.board.mark(row, column, self.role_s) + self.board.mark(row, col, self.role_s) except InvalidMove, e: - print 'invalid move' + print 'you made an invalid move' return - self.send_move(row, column) + self.send_move(row, col) - # XXX check win conditions + # check win conditions + if self.board.check_for_strike(self.role_s, row, col,self.strike): + self.won() + elif self.board.full(): + self.drawn() + else: + self.next_move_id += 1 + + self.their_turn() def send_move(self, row, column): msg = xmpp.Message() + msg.setType('chat') turn = msg.NT.turn - turn.setNamespace('http://jabber.org/protocol/games') + turn.setNamespace(games_ns) move = turn.NT.move - move.setNamespace('http://jabber.org/protocol/games/tictactoe') + move.setNamespace(games_ns+'/tictactoe') move.setAttr('row', str(row)) move.setAttr('col', str(column)) @@ -169,11 +198,49 @@ class TicTacToeSession(stanza_session.StanzaSession): self.send(msg) - self.next_move_id += 1 - - self.their_turn() - class TicTacToeBoard: + def check_for_strike(self, p, r, c, strike): + # up and down, left and right + tallyI = 0 + tally_ = 0 + + # right triangles: L\ , F/ + tallyL = 0 + tallyF = 0 + + # convert real columns to internal columns + r -= 1 + c -= 1 + + for d in xrange(-strike, strike): + # vertical check + try: + tallyI = tallyI + 1 if self.board[r+d][c] == p else 0 + except IndexError: + pass + + # horizontal check + try: + tally_ = tally_ + 1 if self.board[r][c+d] == p else 0 + except IndexError: + pass + + # diagonal checks + try: + tallyL = tallyL + 1 if self.board[r+d][c+d] == p else 0 + except IndexError: + pass + + try: + tallyF = tallyF + 1 if self.board[r+d][c-d] == p else 0 + except IndexError: + pass + + if any([t == strike for t in (tallyL, tallyF, tallyI, tally_)]): + return True + + return False + def __init__(self, session, rows, cols): self.session = session @@ -184,6 +251,15 @@ class TicTacToeBoard: self.setup_window() + # is the board full? + def full(self): + for r in xrange(self.rows): + for c in xrange(self.cols): + if self.board[r][c] == None: + return False + + return True + def setup_window(self): self.win = gtk.Window() @@ -214,6 +290,7 @@ class TicTacToeBoard: self.session.move(row, column) + # this actually draws the board def expose(self, widget, event): win = widget.window