From de23ba2a157a886ad6f5e28b9ab083724c46a58e Mon Sep 17 00:00:00 2001 From: Tomasz Melcer Date: Mon, 17 Jul 2006 13:40:25 +0000 Subject: [PATCH] Moved some code to ease maintenance. --- src/common/commands.py | 232 ++++++++++++++++++++++++++++++ src/common/connection_handlers.py | 115 +-------------- src/common/dataforms.py | 7 + 3 files changed, 241 insertions(+), 113 deletions(-) create mode 100644 src/common/commands.py diff --git a/src/common/commands.py b/src/common/commands.py new file mode 100644 index 000000000..746f1a1a6 --- /dev/null +++ b/src/common/commands.py @@ -0,0 +1,232 @@ +## +## Copyright (C) 2006 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import xmpp +import helpers +import dataforms +import gajim + +class AdHocCommand: + commandnode = 'command' + commandname = 'The Command' + commandfeatures = (xmpp.NS_DATA,) + + @staticmethod + def isVisibleFor(jid): return True + + def __init__(self, conn, jid, sessionid): + self.connection = conn + self.jid = jid + self.sessionid = sessionid + + def buildResponse(self, request, status='executing', defaultaction=None, actions=None): + assert status in ('executing', 'completed', 'canceled') + + response = request.buildReply('result') + cmd = response.addChild('command', { + 'xmlns': xmpp.NS_COMMANDS, + 'sessionid': self.sessionid, + 'node': self.commandnode, + 'status': status}) + if defaultaction is not None or actions is not None: + if defaultaction is not None: + assert defaultaction in ('cancel', 'execute', 'prev', 'next', 'complete') + attrs = {'action': defaultaction} + else: + attrs = {} + + cmd.addChild('actions', attrs, actions) + return response, cmd + + def cancel(self, request): + response, cmd = self.buildResponse(request, status='canceled') + self.connection.send(response) + return False # finish the session + +class ChangeStatusCommand(AdHocCommand): + commandnode = 'change-status' + commandname = 'Change status information' + + def execute(self, request): + # first query... + response, cmd = self.buildResponse(request, defaultaction='execute', actions=['execute']) + + cmd.addChild(node=dataforms.DataForm( + title='Change status', + instructions='Set the presence type and description', + fields=[ + dataforms.DataField('list-single', 'presence-type', + label='Type of presence:', + options=[ + (u'free-for-chat', u'Free for chat'), + (u'online', u'Online'), + (u'away', u'Away'), + (u'xa', u'Extended away'), + (u'dnd', u'Do not disturb'), + (u'offline', u'Offline - disconnect')], + value='online', + required=True), + dataforms.DataField('text-multi', 'presence-desc', + label='Presence description:')])) + + self.connection.send(response) + + # for next invocation + self.execute = self.changestatus + + return True # keep the session + + def changestatus(self, request): + # check if the data is correct + try: + form=dataforms.DataForm(node=request.getTag('command').getTag('x')) + except TypeError: + return False + + try: + presencetype = form['presence-type'] + if not presencetype in ('free-for-chat', 'online', 'away', 'xa', 'dnd', 'offline'): + #raise BadSomething + return + except KeyError: +# raise BadSomething + return + + try: + presencedesc = form['presence-desc'] + except KeyError: + presencedesc = u'' + + response, cmd = self.buildResponse(request, status='completed') + cmd.addChild('note', {}, 'The status has been changed.') + + self.connection.send(response) + + # looking for account name... + accname = None + for acc in gajim.connections.iterkeys(): + if self.connection is gajim.connections[acc]: + accname=acc + assert accname is not None + + gajim.interface.roster.send_status(accname, presencetype, presencedesc) + + return False # finish the session + +class ConnectionCommands: + ''' This class depends on that it is a part of Connection() class. ''' + def __init__(self): + # a list of all commands exposed: node -> command class + self.__commands = {} + for cmdobj in (ChangeStatusCommand,): + self.__commands[cmdobj.commandnode] = cmdobj + + # a list of sessions; keys are tuples (jid, sessionid, node) + self.__sessions = {} + + def commandListQuery(self, con, iq_obj): + iq = iq_obj.buildReply('result') + jid = helpers.get_full_jid_from_iq(iq_obj) + q = iq.getTag('query') + + for node, cmd in self.__commands.iteritems(): + if cmd.isVisibleFor(jid): + q.addChild('item', { + # TODO: find the jid + 'jid': 'our-jid', + 'node': node, + 'name': cmd.commandname}) + + self.connection.send(iq) + + def commandQuery(self, con, iq_obj): + ''' Send disco result for query for command (JEP-0050, example 6.). + Return True if the result was sent, False if not. ''' + jid = helpers.get_full_jid_from_iq(iq_obj) + node = iq_obj.getTagAttr('query', 'node') + + if node not in self.__commands: return False + + cmd = self.__commands[node] + if cmd.isVisibleFor(jid): + iq = iq_obj.buildReply('result') + q = iq.getTag('query') + q.addChild('identity', attrs = {'type': 'command-node', + 'category': 'automation', + 'name': cmd.commandname}) + q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) + for feature in cmd.commandfeatures: + q.addChild('feature', attrs = {'var': feature}) + + self.connection.send(iq) + return True + + return False + + def _CommandExecuteCB(self, con, iq_obj): + jid = helpers.get_full_jid_from_iq(iq_obj) + + cmd = iq_obj.getTag('command') + if cmd is None: return + + node = cmd.getAttr('node') + if node is None: return + + sessionid = cmd.getAttr('sessionid') + if sessionid is None: + # we start a new command session... only if we are visible for the jid + newcmd = self.__commands[node] + if not newcmd.isVisibleFor(jid): + return + + # generate new sessionid + sessionid = self.connection.getAnID() + + # create new instance and run it + obj = newcmd(conn=self.connection, jid=jid, sessionid=sessionid) + rc = obj.execute(iq_obj) + if rc: + self.__sessions[(jid, sessionid, node)] = obj + raise NodeProcessed + else: + # the command is already running, check for it + magictuple = (jid, sessionid, node) + if magictuple not in self.__sessions: + # we don't have this session... ha! + return + + action = cmd.getAttr('action') + obj = self.__sessions[magictuple] + + try: + if action == 'cancel': rc = obj.cancel(iq_obj) + elif action == 'prev': rc = obj.prev(iq_obj) + elif action == 'next': rc = obj.next(iq_obj) + elif action == 'execute': rc = obj.execute(iq_obj) + elif action == 'complete': rc = obj.complete(iq_obj) + else: + # action is wrong. stop the session, send error + del self.__sessions[magictuple] + return + except AttributeError: + # the command probably doesn't handle invoked action... + # stop the session, return error + del self.__sessions[magictuple] + return + + # delete the session if rc is False + if not rc: + del self.__sessions[magictuple] + + raise xmpp.NodeProcessed + diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index f52104e68..522ef46ec 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -32,6 +32,8 @@ import common.xmpp from common import GnuPG from common import helpers from common import gajim +from common import dataforms +from common.commands import ConnectionCommands STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', 'invisible'] @@ -1054,119 +1056,6 @@ class ConnectionVcard: else: self.dispatch('VCARD', vcard) -class AdHocCommand: - commandnode = 'command' - commandname = 'The Command' - commandfeatures = (common.xmpp.NS_DATA,) - - @staticmethod - def isVisibleFor(jid): return True - -class ChangeStatusCommand(AdHocCommand): - commandnode = 'change-status' - commandname = 'Change status information' - def __init__(self, sessiondata): - pass - -class ConnectionCommands: - def __init__(self): - # a list of all commands exposed - self.__commands = {} - for cmdobj in (ChangeStatusCommand,): - self.__commands[cmdobj.commandnode] = cmdobj - - # a list of sessions; keys are tuples (jid, sessionid, node) - self.__sessions = {} - - def commandListQuery(self, con, iq_obj): - iq = iq_obj.buildReply('result') - jid = helpers.get_full_jid_from_iq(iq_obj) - q = iq.getTag('query') - - for node, cmd in self.__commands.iter_items(): - if cmd.isVisibleFor(jid): - q.addChild('item', { - 'jid': 'our-jid', - 'node': node, - 'name': cmd.commandname}) - - self.connection.send(iq) - - def commandQuery(self, con, iq_obj): - ''' Send disco result for query for command (JEP-0050, example 6.). - Return True if the result was sent, False if not. ''' - jid = helpers.get_full_jid_from_iq(iq_obj) - node = iq_obj.getTagAttr('query', 'node') - - if node not in self.__commands: return False - - cmd = self.__commands[node] - if cmd.isVisibleFor(jid): - iq = iq_obj.buildReply('result') - q = iq.getTag('query') - q.addChild('identity', attrs = {'type': 'command-node', - 'category': 'automation', - 'name': cmd.commandname}) - q.addChild('feature', attrs = {'var': common.xmpp.NS_COMMANDS}) - for feature in cmd.commandfeatures: - q.addChild('feature', attrs = {'var': feature}) - - self.connection.send(iq) - return True - - return False - - def _CommandExecuteCB(self, con, iq_obj): - jid = helpers.get_full_jid_from_iq(iq_obj) - - cmd = iq_obj.getTag('command') - if cmd is None: return - - node = cmd.getAttr('node') - if node is None: return - - sessionid = cmd.getAttr('sessionid') - if sessionid is None: - # we start a new command session... only if we are visible for the jid - newcmd = self.__commands[node] - if not newcmd.isVisibleFor(jid): - return - - # generate new sessionid - sessionid = self.connection.getAnID() - - # create new instance and run it - obj = newcmd(jid=jid, sessionid=sessionid) - rc = obj.execute() - if rc: - self.__sessions[(jid, sessionid, node)] = obj - raise NodeProcessed - else: - # the command is already running, check for it - magictuple = (jid, sessionid, node) - if magictuple not in self.__sessions: - # we don't have this session... ha! - return - - action = cmd.getAttr('action') - obj = self.__sessions[magictuple] - - if action == 'cancel': rc = obj.cancel(iq_obj) - elif action == 'prev': rc = obj.prev(iq_obj) - elif action == 'next': rc = obj.next(iq_obj) - elif action == 'execute': rc = obj.execute(iq_obj) - elif action == 'complete': rc = obj.complete(iq_obj) - else: - # action is wrong. stop the session, send error - del self.__sessions[magictuple] - return - - # delete the session if rc is False - if not rc: - del self.__sessions[magictuple] - - raise NodeProcessed - class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands): def __init__(self): ConnectionVcard.__init__(self) diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 5acf452ad..6c1e76240 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -287,6 +287,13 @@ class DataForm(xmpp.Node, object): return raise KeyError, "This form does not contain %r field." % var + def __contains__(self, name): + for field in self.iter_fields(): + if field.var==name: + return True + else: + return False + class DataField(xmpp.Node, object): def __init__(self, typ=None,var=None, value=None, label=None, desc=None, required=None, options=None, node=None):