root/branches/gajim_0.11.1/src/common/commands.py

Revision 8632, 10.5 kB (checked in by roidelapluie, 15 months ago)

add a now argument to the send function, so that stanza is sent
instantly instead of added to queue. Use it to send answer to adhoc
command when we disconnect. fixes #3008 and #2808

Line 
1##
2## Copyright (C) 2006 Gajim Team
3##
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published
6## by the Free Software Foundation; version 2 only.
7##
8## This program is distributed in the hope that it will be useful,
9## but WITHOUT ANY WARRANTY; without even the implied warranty of
10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11## GNU General Public License for more details.
12##
13
14import xmpp
15import helpers
16import dataforms
17import gajim
18
19class AdHocCommand:
20        commandnode = 'command'
21        commandname = 'The Command'
22        commandfeatures = (xmpp.NS_DATA,)
23
24        @staticmethod
25        def isVisibleFor(samejid):
26                ''' This returns True if that command should be visible and invokable
27                for others.
28                samejid - True when command is invoked by an entity with the same bare
29                jid.'''
30                return True
31
32        def __init__(self, conn, jid, sessionid):
33                self.connection = conn
34                self.jid = jid
35                self.sessionid = sessionid
36
37        def buildResponse(self, request, status = 'executing', defaultaction = None,
38        actions = None):
39                assert status in ('executing', 'completed', 'canceled')
40
41                response = request.buildReply('result')
42                cmd = response.addChild('command', {
43                        'xmlns': xmpp.NS_COMMANDS,
44                        'sessionid': self.sessionid,
45                        'node': self.commandnode,
46                        'status': status})
47                if defaultaction is not None or actions is not None:
48                        if defaultaction is not None:
49                                assert defaultaction in ('cancel', 'execute', 'prev', 'next',
50                                        'complete')
51                                attrs = {'action': defaultaction}
52                        else:
53                                attrs = {}
54
55                        cmd.addChild('actions', attrs, actions)
56                return response, cmd
57
58        def badRequest(self, stanza):
59                self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \
60                        ' bad-request'))
61
62        def cancel(self, request):
63                response, cmd = self.buildResponse(request, status = 'canceled')
64                self.connection.connection.send(response)
65                return False    # finish the session
66
67class ChangeStatusCommand(AdHocCommand):
68        commandnode = 'change-status'
69        commandname = _('Change status information')
70
71        @staticmethod
72        def isVisibleFor(samejid):
73                ''' Change status is visible only if the entity has the same bare jid. '''
74                return samejid
75
76        def execute(self, request):
77                # first query...
78                response, cmd = self.buildResponse(request, defaultaction = 'execute',
79                        actions = ['execute'])
80               
81                cmd.addChild(node = dataforms.SimpleDataForm(
82                        title = _('Change status'),
83                        instructions = _('Set the presence type and description'),
84                        fields = [
85                                dataforms.Field('list-single',
86                                        var = 'presence-type',
87                                        label = 'Type of presence:',
88                                        options = [
89                                                (u'free-for-chat', _('Free for chat')),
90                                                (u'online', _('Online')),
91                                                (u'away', _('Away')),
92                                                (u'xa', _('Extended away')),
93                                                (u'dnd', _('Do not disturb')),
94                                                (u'offline', _('Offline - disconnect'))],
95                                        value = 'online',
96                                        required = True),
97                                dataforms.Field('text-multi',
98                                        var = 'presence-desc',
99                                        label = _('Presence description:'))]))
100
101                self.connection.connection.send(response)
102
103                # for next invocation
104                self.execute = self.changestatus
105               
106                return True     # keep the session
107
108        def changestatus(self, request):
109                # check if the data is correct
110                try:
111                        form = dataforms.SimpleDataForm(extend = request.getTag('command').\
112                                getTag('x'))
113                except:
114                        self.badRequest(request)
115                        return False
116
117                try:
118                        presencetype = form['presence-type'].value
119                        if not presencetype in \
120                        ('free-for-chat', 'online', 'away', 'xa', 'dnd', 'offline'):
121                                self.badRequest(request)
122                                return False
123                except: # KeyError if there's no presence-type field in form or
124                        # AttributeError if that field is of wrong type
125                        self.badRequest(request)
126                        return False
127
128                try:
129                        presencedesc = form['presence-desc'].value
130                except: # same exceptions as in last comment
131                        presencedesc = u''
132
133                response, cmd = self.buildResponse(request, status = 'completed')
134                cmd.addChild('note', {}, _('The status has been changed.'))
135
136                # if going offline, we need to push response so it won't go into
137                # queue and disappear
138                self.connection.connection.send(response, now = presencetype == 'offline')
139
140                # send new status
141                gajim.interface.roster.send_status(self.connection.name, presencetype,
142                        presencedesc)
143
144                return False    # finish the session
145
146def find_current_groupchats(account):
147        import message_control
148        rooms = []
149        for gc_control in gajim.interface.msg_win_mgr.get_controls(
150        message_control.TYPE_GC):
151                acct = gc_control.account
152                # check if account is the good one
153                if acct != account:
154                        continue
155                room_jid = gc_control.room_jid
156                nick = gc_control.nick
157                if gajim.gc_connected[acct].has_key(room_jid) and \
158                gajim.gc_connected[acct][room_jid]:
159                        rooms.append((room_jid, nick,))
160        return rooms
161
162
163class LeaveGroupchatsCommand(AdHocCommand):
164        commandnode = 'leave-groupchats'
165        commandname = 'Leave Groupchats'
166
167        @staticmethod
168        def isVisibleFor(samejid):
169                ''' Change status is visible only if the entity has the same bare jid. '''
170                return samejid
171
172        def execute(self, request):
173                # first query...
174                response, cmd = self.buildResponse(request, defaultaction = 'execute',
175                        actions=['execute'])
176                options = []
177                account = self.connection.name
178                for gc in find_current_groupchats(account):
179                        options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
180                                {'nickname': gc[1], 'room_jid': gc[0]}))
181                if not len(options):
182                        response, cmd = self.buildResponse(request, status = 'completed')
183                        cmd.addChild('note', {}, _('You have not joined a groupchat.'))
184               
185                        self.connection.connection.send(response)
186                        return False
187
188                cmd.addChild(node=dataforms.SimpleDataForm(
189                        title = _('Leave Groupchats'),
190                        instructions = _('Choose the groupchats you want to leave'),
191                        fields=[
192                                dataforms.Field('list-multi',
193                                        var = 'groupchats',
194                                        label = _('Groupchats'),
195                                        options = options,
196                                        required = True)]))
197
198                self.connection.connection.send(response)
199
200                # for next invocation
201                self.execute = self.leavegroupchats
202               
203                return True     # keep the session
204
205        def leavegroupchats(self, request):
206                # check if the data is correct
207                try:
208                        form = dataforms.SimpleDataForm(extend = request.getTag('command').\
209                                getTag('x'))
210                except:
211                        self.badRequest(request)
212                        return False
213
214                try:
215                        gc = form['groupchats'].values
216                except: # KeyError if there's no presence-type field in form or
217                        # AttributeError if that field is of wrong type
218                        self.badRequest(request)
219                        return False
220                account = self.connection.name
221                try:
222                        for room_jid in gc:
223                                gc_control = gajim.interface.msg_win_mgr.get_control(room_jid,
224                                        account)
225                                gc_control.parent_win.remove_tab(gc_control, None, force = True)
226                except: # KeyError if there's no presence-type field in form or
227                        self.badRequest(request)
228                        return False
229                response, cmd = self.buildResponse(request, status = 'completed')
230                note = _('You leaved the following groupchats:')
231                for room_jid in gc:
232                        note += '\n\t' + room_jid
233                cmd.addChild('note', {}, note)
234
235                self.connection.connection.send(response)
236                return False
237
238
239class ConnectionCommands:
240        ''' This class depends on that it is a part of Connection() class. '''
241        def __init__(self):
242                # a list of all commands exposed: node -> command class
243                self.__commands = {}
244                for cmdobj in (ChangeStatusCommand, LeaveGroupchatsCommand):
245                        self.__commands[cmdobj.commandnode] = cmdobj
246
247                # a list of sessions; keys are tuples (jid, sessionid, node)
248                self.__sessions = {}
249
250        def getOurBareJID(self):
251                return gajim.get_jid_from_account(self.name)
252
253        def isSameJID(self, jid):
254                ''' Tests if the bare jid given is the same as our bare jid. '''
255                return xmpp.JID(jid).getStripped() == self.getOurBareJID()
256
257        def commandListQuery(self, con, iq_obj):
258                iq = iq_obj.buildReply('result')
259                jid = helpers.get_full_jid_from_iq(iq_obj)
260                q = iq.getTag('query')
261
262                for node, cmd in self.__commands.iteritems():
263                        if cmd.isVisibleFor(self.isSameJID(jid)):
264                                q.addChild('item', {
265                                        # TODO: find the jid
266                                        'jid': self.getOurBareJID() + u'/' + self.server_resource,
267                                        'node': node,
268                                        'name': cmd.commandname})
269
270                self.connection.send(iq)
271
272        def commandQuery(self, con, iq_obj):
273                ''' Send disco result for query for command (JEP-0050, example 6.).
274                Return True if the result was sent, False if not. '''
275                jid = helpers.get_full_jid_from_iq(iq_obj)
276                node = iq_obj.getTagAttr('query', 'node')
277
278                if node not in self.__commands: return False
279
280                cmd = self.__commands[node]
281                if cmd.isVisibleFor(self.isSameJID(jid)):
282                        iq = iq_obj.buildReply('result')
283                        q = iq.getTag('query')
284                        q.addChild('identity', attrs = {'type': 'command-node',
285                                'category': 'automation',
286                                'name': cmd.commandname})
287                        q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS})
288                        for feature in cmd.commandfeatures:
289                                q.addChild('feature', attrs = {'var': feature})
290
291                        self.connection.send(iq)
292                        return True
293
294                return False
295
296        def _CommandExecuteCB(self, con, iq_obj):
297                jid = helpers.get_full_jid_from_iq(iq_obj)
298
299                cmd = iq_obj.getTag('command')
300                if cmd is None: return
301
302                node = cmd.getAttr('node')
303                if node is None: return
304
305                sessionid = cmd.getAttr('sessionid')
306                if sessionid is None:
307                        # we start a new command session... only if we are visible for the jid
308                        # and command exist
309                        if node not in self.__commands.keys():
310                                self.connection.send(
311                                        xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found'))
312                                raise xmpp.NodeProcessed
313
314                        newcmd = self.__commands[node]
315                        if not newcmd.isVisibleFor(self.isSameJID(jid)):
316                                return
317
318                        # generate new sessionid
319                        sessionid = self.connection.getAnID()
320
321                        # create new instance and run it
322                        obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
323                        rc = obj.execute(iq_obj)
324                        if rc:
325                                self.__sessions[(jid, sessionid, node)] = obj
326                        raise xmpp.NodeProcessed
327                else:
328                        # the command is already running, check for it
329                        magictuple = (jid, sessionid, node)
330                        if magictuple not in self.__sessions:
331                                # we don't have this session... ha!
332                                return
333
334                        action = cmd.getAttr('action')
335                        obj = self.__sessions[magictuple]
336
337                        try:
338                                if action == 'cancel':
339                                        rc = obj.cancel(iq_obj)
340                                elif action == 'prev':
341                                        rc = obj.prev(iq_obj)
342                                elif action == 'next':
343                                        rc = obj.next(iq_obj)
344                                elif action == 'execute' or action is None:
345                                        rc = obj.execute(iq_obj)
346                                elif action == 'complete':
347                                        rc = obj.complete(iq_obj)
348                                else:
349                                        # action is wrong. stop the session, send error
350                                        raise AttributeError
351                        except AttributeError:
352                                # the command probably doesn't handle invoked action...
353                                # stop the session, return error
354                                del self.__sessions[magictuple]
355                                return
356
357                        # delete the session if rc is False
358                        if not rc:
359                                del self.__sessions[magictuple]
360
361                        raise xmpp.NodeProcessed
Note: See TracBrowser for help on using the browser.