Package common :: Module commands
[hide private]
[frames] | no frames]

Source Code for Module common.commands

  1  # -*- coding:utf-8 -*- 
  2  ## src/common/commands.py 
  3  ## 
  4  ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> 
  5  ## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org> 
  6  ## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org> 
  7  ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> 
  8  ##                    Stephan Erb <steve-e AT h3c.de> 
  9  ## 
 10  ## This file is part of Gajim. 
 11  ## 
 12  ## Gajim is free software; you can redistribute it and/or modify 
 13  ## it under the terms of the GNU General Public License as published 
 14  ## by the Free Software Foundation; version 3 only. 
 15  ## 
 16  ## Gajim is distributed in the hope that it will be useful, 
 17  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 18  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 19  ## GNU General Public License for more details. 
 20  ## 
 21  ## You should have received a copy of the GNU General Public License 
 22  ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 
 23  ## 
 24   
 25  import xmpp 
 26  import helpers 
 27  import dataforms 
 28  import gajim 
29 30 -class AdHocCommand:
31 commandnode = 'command' 32 commandname = 'The Command' 33 commandfeatures = (xmpp.NS_DATA,) 34 35 @staticmethod
36 - def isVisibleFor(samejid):
37 """ 38 This returns True if that command should be visible and invokable for 39 others 40 41 samejid - True when command is invoked by an entity with the same bare 42 jid. 43 """ 44 return True
45
46 - def __init__(self, conn, jid, sessionid):
47 self.connection = conn 48 self.jid = jid 49 self.sessionid = sessionid
50
51 - def buildResponse(self, request, status = 'executing', defaultaction = None, 52 actions = None):
53 assert status in ('executing', 'completed', 'canceled') 54 55 response = request.buildReply('result') 56 cmd = response.addChild('command', namespace=xmpp.NS_COMMANDS, attrs={ 57 'sessionid': self.sessionid, 58 'node': self.commandnode, 59 'status': status}) 60 if defaultaction is not None or actions is not None: 61 if defaultaction is not None: 62 assert defaultaction in ('cancel', 'execute', 'prev', 'next', 63 'complete') 64 attrs = {'action': defaultaction} 65 else: 66 attrs = {} 67 68 cmd.addChild('actions', attrs, actions) 69 return response, cmd
70
71 - def badRequest(self, stanza):
72 self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS + \ 73 ' bad-request'))
74
75 - def cancel(self, request):
76 response = self.buildResponse(request, status = 'canceled')[0] 77 self.connection.connection.send(response) 78 return False # finish the session
79
80 -class ChangeStatusCommand(AdHocCommand):
81 commandnode = 'change-status' 82 commandname = _('Change status information') 83 84 @staticmethod
85 - def isVisibleFor(samejid):
86 """ 87 Change status is visible only if the entity has the same bare jid 88 """ 89 return samejid
90
91 - def execute(self, request):
92 # first query... 93 response, cmd = self.buildResponse(request, defaultaction = 'execute', 94 actions = ['execute']) 95 96 cmd.addChild(node = dataforms.SimpleDataForm( 97 title = _('Change status'), 98 instructions = _('Set the presence type and description'), 99 fields = [ 100 dataforms.Field('list-single', 101 var = 'presence-type', 102 label = 'Type of presence:', 103 options = [ 104 (u'chat', _('Free for chat')), 105 (u'online', _('Online')), 106 (u'away', _('Away')), 107 (u'xa', _('Extended away')), 108 (u'dnd', _('Do not disturb')), 109 (u'offline', _('Offline - disconnect'))], 110 value = 'online', 111 required = True), 112 dataforms.Field('text-multi', 113 var = 'presence-desc', 114 label = _('Presence description:'))])) 115 116 self.connection.connection.send(response) 117 118 # for next invocation 119 self.execute = self.changestatus 120 121 return True # keep the session
122
123 - def changestatus(self, request):
124 # check if the data is correct 125 try: 126 form = dataforms.SimpleDataForm(extend = request.getTag('command').\ 127 getTag('x')) 128 except Exception: 129 self.badRequest(request) 130 return False 131 132 try: 133 presencetype = form['presence-type'].value 134 if not presencetype in \ 135 ('chat', 'online', 'away', 'xa', 'dnd', 'offline'): 136 self.badRequest(request) 137 return False 138 except Exception: # KeyError if there's no presence-type field in form or 139 # AttributeError if that field is of wrong type 140 self.badRequest(request) 141 return False 142 143 try: 144 presencedesc = form['presence-desc'].value 145 except Exception: # same exceptions as in last comment 146 presencedesc = u'' 147 148 response, cmd = self.buildResponse(request, status = 'completed') 149 cmd.addChild('note', {}, _('The status has been changed.')) 150 151 # if going offline, we need to push response so it won't go into 152 # queue and disappear 153 self.connection.connection.send(response, now = presencetype == 'offline') 154 155 # send new status 156 gajim.interface.roster.send_status(self.connection.name, presencetype, 157 presencedesc) 158 159 return False # finish the session
160
161 -def find_current_groupchats(account):
162 import message_control 163 rooms = [] 164 for gc_control in gajim.interface.msg_win_mgr.get_controls( 165 message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\ 166 values(): 167 acct = gc_control.account 168 # check if account is the good one 169 if acct != account: 170 continue 171 room_jid = gc_control.room_jid 172 nick = gc_control.nick 173 if room_jid in gajim.gc_connected[acct] and \ 174 gajim.gc_connected[acct][room_jid]: 175 rooms.append((room_jid, nick,)) 176 return rooms
177
178 179 -class LeaveGroupchatsCommand(AdHocCommand):
180 commandnode = 'leave-groupchats' 181 commandname = _('Leave Groupchats') 182 183 @staticmethod
184 - def isVisibleFor(samejid):
185 """ 186 Change status is visible only if the entity has the same bare jid 187 """ 188 return samejid
189
190 - def execute(self, request):
191 # first query... 192 response, cmd = self.buildResponse(request, defaultaction = 'execute', 193 actions=['execute']) 194 options = [] 195 account = self.connection.name 196 for gc in find_current_groupchats(account): 197 options.append((u'%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \ 198 {'nickname': gc[1], 'room_jid': gc[0]})) 199 if not len(options): 200 response, cmd = self.buildResponse(request, status = 'completed') 201 cmd.addChild('note', {}, _('You have not joined a groupchat.')) 202 203 self.connection.connection.send(response) 204 return False 205 206 cmd.addChild(node=dataforms.SimpleDataForm( 207 title = _('Leave Groupchats'), 208 instructions = _('Choose the groupchats you want to leave'), 209 fields=[ 210 dataforms.Field('list-multi', 211 var = 'groupchats', 212 label = _('Groupchats'), 213 options = options, 214 required = True)])) 215 216 self.connection.connection.send(response) 217 218 # for next invocation 219 self.execute = self.leavegroupchats 220 221 return True # keep the session
222
223 - def leavegroupchats(self, request):
224 # check if the data is correct 225 try: 226 form = dataforms.SimpleDataForm(extend = request.getTag('command').\ 227 getTag('x')) 228 except Exception: 229 self.badRequest(request) 230 return False 231 232 try: 233 gc = form['groupchats'].values 234 except Exception: # KeyError if there's no groupchats in form 235 self.badRequest(request) 236 return False 237 account = self.connection.name 238 try: 239 for room_jid in gc: 240 gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid, 241 account) 242 if not gc_control: 243 gc_control = gajim.interface.minimized_controls[account]\ 244 [room_jid] 245 gc_control.shutdown() 246 gajim.interface.roster.remove_groupchat(room_jid, account) 247 continue 248 gc_control.parent_win.remove_tab(gc_control, None, force = True) 249 except Exception: # KeyError if there's no such room opened 250 self.badRequest(request) 251 return False 252 response, cmd = self.buildResponse(request, status = 'completed') 253 note = _('You left the following groupchats:') 254 for room_jid in gc: 255 note += '\n\t' + room_jid 256 cmd.addChild('note', {}, note) 257 258 self.connection.connection.send(response) 259 return False
260
261 262 -class ForwardMessagesCommand(AdHocCommand):
263 # http://www.xmpp.org/extensions/xep-0146.html#forward 264 commandnode = 'forward-messages' 265 commandname = _('Forward unread messages') 266 267 @staticmethod
268 - def isVisibleFor(samejid):
269 """ 270 Change status is visible only if the entity has the same bare jid 271 """ 272 return samejid
273
274 - def execute(self, request):
275 account = self.connection.name 276 # Forward messages 277 events = gajim.events.get_events(account, types=['chat', 'normal']) 278 j, resource = gajim.get_room_and_nick_from_fjid(self.jid) 279 for jid in events: 280 for event in events[jid]: 281 self.connection.send_message(j, event.parameters[0], '', 282 type_=event.type_, subject=event.parameters[1], 283 resource=resource, forward_from=jid, delayed=event.time_) 284 285 # Inform other client of completion 286 response, cmd = self.buildResponse(request, status = 'completed') 287 cmd.addChild('note', {}, _('All unread messages have been forwarded.')) 288 289 self.connection.connection.send(response) 290 291 return False # finish the session
292
293 -class FwdMsgThenDisconnectCommand(AdHocCommand):
294 commandnode = 'fwd-msd-disconnect' 295 commandname = _('Forward unread message then disconnect') 296 297 @staticmethod
298 - def isVisibleFor(samejid):
299 """ 300 Change status is visible only if the entity has the same bare jid 301 """ 302 return samejid
303
304 - def execute(self, request):
305 account = self.connection.name 306 # Forward messages 307 events = gajim.events.get_events(account, types=['chat', 'normal']) 308 j, resource = gajim.get_room_and_nick_from_fjid(self.jid) 309 for jid in events: 310 for event in events[jid]: 311 self.connection.send_message(j, event.parameters[0], '', 312 type_=event.type_, subject=event.parameters[1], 313 resource=resource, forward_from=jid, delayed=event.time_, 314 now=True) 315 316 response, cmd = self.buildResponse(request, status = 'completed') 317 cmd.addChild('note', {}, _('The status has been changed.')) 318 319 # if going offline, we need to push response so it won't go into 320 # queue and disappear 321 self.connection.connection.send(response, now = True) 322 323 # send new status 324 gajim.interface.roster.send_status(self.connection.name, 'offline', '') 325 # finish the session 326 return False
327
328 -class ConnectionCommands:
329 """ 330 This class depends on that it is a part of Connection() class 331 """ 332
333 - def __init__(self):
334 # a list of all commands exposed: node -> command class 335 self.__commands = {} 336 for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand, 337 LeaveGroupchatsCommand, FwdMsgThenDisconnectCommand): 338 self.__commands[cmdobj.commandnode] = cmdobj 339 340 # a list of sessions; keys are tuples (jid, sessionid, node) 341 self.__sessions = {}
342
343 - def getOurBareJID(self):
344 return gajim.get_jid_from_account(self.name)
345
346 - def isSameJID(self, jid):
347 """ 348 Test if the bare jid given is the same as our bare jid 349 """ 350 return xmpp.JID(jid).getStripped() == self.getOurBareJID()
351
352 - def commandListQuery(self, con, iq_obj):
353 iq = iq_obj.buildReply('result') 354 jid = helpers.get_full_jid_from_iq(iq_obj) 355 q = iq.getTag('query') 356 # buildReply don't copy the node attribute. Re-add it 357 q.setAttr('node', xmpp.NS_COMMANDS) 358 359 for node, cmd in self.__commands.iteritems(): 360 if cmd.isVisibleFor(self.isSameJID(jid)): 361 q.addChild('item', { 362 # TODO: find the jid 363 'jid': self.getOurBareJID() + u'/' + self.server_resource, 364 'node': node, 365 'name': cmd.commandname}) 366 367 self.connection.send(iq)
368
369 - def commandInfoQuery(self, con, iq_obj):
370 """ 371 Send disco#info result for query for command (JEP-0050, example 6.). 372 Return True if the result was sent, False if not 373 """ 374 jid = helpers.get_full_jid_from_iq(iq_obj) 375 node = iq_obj.getTagAttr('query', 'node') 376 377 if node not in self.__commands: return False 378 379 cmd = self.__commands[node] 380 if cmd.isVisibleFor(self.isSameJID(jid)): 381 iq = iq_obj.buildReply('result') 382 q = iq.getTag('query') 383 q.addChild('identity', attrs = {'type': 'command-node', 384 'category': 'automation', 385 'name': cmd.commandname}) 386 q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) 387 for feature in cmd.commandfeatures: 388 q.addChild('feature', attrs = {'var': feature}) 389 390 self.connection.send(iq) 391 return True 392 393 return False
394
395 - def commandItemsQuery(self, con, iq_obj):
396 """ 397 Send disco#items result for query for command. Return True if the result 398 was sent, False if not. 399 """ 400 jid = helpers.get_full_jid_from_iq(iq_obj) 401 node = iq_obj.getTagAttr('query', 'node') 402 403 if node not in self.__commands: return False 404 405 cmd = self.__commands[node] 406 if cmd.isVisibleFor(self.isSameJID(jid)): 407 iq = iq_obj.buildReply('result') 408 self.connection.send(iq) 409 return True 410 411 return False
412
413 - def _CommandExecuteCB(self, con, iq_obj):
414 jid = helpers.get_full_jid_from_iq(iq_obj) 415 416 cmd = iq_obj.getTag('command') 417 if cmd is None: return 418 419 node = cmd.getAttr('node') 420 if node is None: return 421 422 sessionid = cmd.getAttr('sessionid') 423 if sessionid is None: 424 # we start a new command session... only if we are visible for the jid 425 # and command exist 426 if node not in self.__commands.keys(): 427 self.connection.send( 428 xmpp.Error(iq_obj, xmpp.NS_STANZAS + ' item-not-found')) 429 raise xmpp.NodeProcessed 430 431 newcmd = self.__commands[node] 432 if not newcmd.isVisibleFor(self.isSameJID(jid)): 433 return 434 435 # generate new sessionid 436 sessionid = self.connection.getAnID() 437 438 # create new instance and run it 439 obj = newcmd(conn = self, jid = jid, sessionid = sessionid) 440 rc = obj.execute(iq_obj) 441 if rc: 442 self.__sessions[(jid, sessionid, node)] = obj 443 raise xmpp.NodeProcessed 444 else: 445 # the command is already running, check for it 446 magictuple = (jid, sessionid, node) 447 if magictuple not in self.__sessions: 448 # we don't have this session... ha! 449 return 450 451 action = cmd.getAttr('action') 452 obj = self.__sessions[magictuple] 453 454 try: 455 if action == 'cancel': 456 rc = obj.cancel(iq_obj) 457 elif action == 'prev': 458 rc = obj.prev(iq_obj) 459 elif action == 'next': 460 rc = obj.next(iq_obj) 461 elif action == 'execute' or action is None: 462 rc = obj.execute(iq_obj) 463 elif action == 'complete': 464 rc = obj.complete(iq_obj) 465 else: 466 # action is wrong. stop the session, send error 467 raise AttributeError 468 except AttributeError: 469 # the command probably doesn't handle invoked action... 470 # stop the session, return error 471 del self.__sessions[magictuple] 472 return 473 474 # delete the session if rc is False 475 if not rc: 476 del self.__sessions[magictuple] 477 478 raise xmpp.NodeProcessed
479