[Git][gajim/gajim][master] Refactor AdHocCommands into own module
Philipp Hörist
gitlab at dev.gajim.org
Sun Jul 8 19:40:16 CEST 2018
Philipp Hörist pushed to branch master at gajim / gajim
Commits:
a2d7283e by Philipp Hörist at 2018-07-08T19:39:57+02:00
Refactor AdHocCommands into own module
- - - - -
7 changed files:
- gajim/adhoc_commands.py
- − gajim/common/commands.py
- gajim/common/connection_handlers.py
- gajim/common/modules/__init__.py
- + gajim/common/modules/adhoc_commands.py
- gajim/common/zeroconf/connection_handlers_zeroconf.py
- gajim/common/zeroconf/connection_zeroconf.py
Changes:
=====================================
gajim/adhoc_commands.py
=====================================
--- a/gajim/adhoc_commands.py
+++ b/gajim/adhoc_commands.py
@@ -25,12 +25,12 @@
# FIXME: think if we need caching command list. it may be wrong if there will
# be entities that often change the list, it may be slow to fetch it every time
-from gi.repository import GLib
from gi.repository import Gtk
import nbxmpp
from gajim.common import app
from gajim.common import dataforms
+from gajim.common import ged
from gajim import gtkgui_helpers
from gajim import dialogs
@@ -55,7 +55,7 @@ class CommandWindow:
"""
# an account object
- self.account = app.connections[account]
+ self._con = app.connections[account]
self.jid = jid
self.commandnode = commandnode
self.data_form_widget = None
@@ -87,6 +87,14 @@ class CommandWindow:
column = Gtk.TreeViewColumn("Command", renderer, text=0)
self.command_treeview.append_column(column)
+ app.ged.register_event_handler(
+ 'adhoc-command-error', ged.CORE, self._on_command_error)
+ app.ged.register_event_handler(
+ 'adhoc-command-list', ged.CORE, self._on_command_list)
+ app.ged.register_event_handler('adhoc-command-action-response',
+ ged.CORE,
+ self._on_action_response)
+
self.initiate()
def initiate(self):
@@ -157,8 +165,13 @@ class CommandWindow:
return False
def on_adhoc_commands_window_destroy(self, *anything):
- # TODO: do all actions that are needed to remove this object from memory
- pass
+ app.ged.remove_event_handler(
+ 'adhoc-command-error', ged.CORE, self._on_command_error)
+ app.ged.remove_event_handler(
+ 'adhoc-command-list', ged.CORE, self._on_command_list)
+ app.ged.remove_event_handler('adhoc-command-action-response',
+ ged.CORE,
+ self._on_action_response)
def on_adhoc_commands_window_delete_event(self, *anything):
if self.stage_window_delete_cb:
@@ -190,7 +203,7 @@ class CommandWindow:
self.finish_button.set_sensitive(False)
# request command list
- self.request_command_list()
+ self._con.get_module('AdHocCommands').request_command_list(self.jid)
self.retrieving_commands_spinner.start()
# setup the callbacks
@@ -304,7 +317,8 @@ class CommandWindow:
return
def on_yes(button):
- self.send_cancel()
+ self._con.get_module('AdHocCommands').send_cancel(
+ self.jid, self.commandnode, self.sessionid)
dialog.destroy()
cb()
@@ -371,7 +385,9 @@ class CommandWindow:
self.finish_button.set_sensitive(False)
self.sending_form_spinner.start()
- self.send_command(action)
+ self._con.get_module('AdHocCommands').send_command(
+ self.jid, self.commandnode, self.sessionid,
+ self.data_form_widget.data_form, action)
def stage3_next_form(self, command):
if not isinstance(command, nbxmpp.Node):
@@ -527,85 +543,15 @@ class CommandWindow:
def stage5_restart_button_clicked(self, widget):
self.restart()
-# handling xml stanzas
- def request_command_list(self):
- """
- Request the command list. Change stage on delivery
- """
- query = nbxmpp.Iq(typ='get', to=nbxmpp.JID(self.jid),
- queryNS=nbxmpp.NS_DISCO_ITEMS)
- query.setQuerynode(nbxmpp.NS_COMMANDS)
-
- def callback(response):
- '''Called on response to query.'''
- # FIXME: move to connection_handlers.py
- # is error => error stage
- error = response.getError()
- if error:
- # extracting error description
- self.stage5(errorid=error)
- return
-
- # no commands => no commands stage
- # commands => command selection stage
- query = response.getTag('query')
- if query and query.getAttr('node') == nbxmpp.NS_COMMANDS:
- items = query.getTags('item')
- else:
- items = []
- if len(items)==0:
- self.commandlist = []
- self.stage4()
- else:
- self.commandlist = [(t.getAttr('node'), t.getAttr('name')) \
- for t in items]
- self.stage2()
-
- self.account.connection.SendAndCallForResponse(query, callback)
-
- def send_command(self, action='execute'):
- """
- Send the command with data form. Wait for reply
- """
- # create the stanza
- assert action in ('execute', 'prev', 'next', 'complete')
-
- stanza = nbxmpp.Iq(typ='set', to=self.jid)
- cmdnode = stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
- attrs={'node':self.commandnode, 'action':action})
+ def _on_command_error(self, obj):
+ self.stage5(errorid=obj.error)
- if self.sessionid:
- cmdnode.setAttr('sessionid', self.sessionid)
-
- if self.data_form_widget.data_form:
- cmdnode.addChild(node=self.data_form_widget.data_form.get_purged())
-
- def callback(response):
- # FIXME: move to connection_handlers.py
- err = response.getError()
- if err:
- self.stage5(errorid = err)
- else:
- self.stage3_next_form(response.getTag('command'))
-
- self.account.connection.SendAndCallForResponse(stanza, callback)
-
- def send_cancel(self):
- """
- Send the command with action='cancel'
- """
- assert self.commandnode
- if self.sessionid and self.account.connection:
- # we already have sessionid, so the service sent at least one reply.
- stanza = nbxmpp.Iq(typ='set', to=self.jid)
- stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS, attrs={
- 'node':self.commandnode,
- 'sessionid':self.sessionid,
- 'action':'cancel'
- })
-
- self.account.connection.send(stanza)
+ def _on_command_list(self, obj):
+ self.commandlist = obj.commandlist
+ if not self.commandlist:
+ self.stage4()
else:
- # we did not received any reply from service;
- # FIXME: we should wait and then send cancel; for now we do nothing
- pass
+ self.stage2()
+
+ def _on_action_response(self, obj):
+ self.stage3_next_form(obj.command)
=====================================
gajim/common/commands.py deleted
=====================================
--- a/gajim/common/commands.py
+++ /dev/null
@@ -1,433 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/commands.py
-##
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## This file is part of Gajim.
-##
-## Gajim 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 3 only.
-##
-## Gajim 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.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import nbxmpp
-from gajim.common import helpers
-from gajim.common import dataforms
-from gajim.common import app
-
-import logging
-log = logging.getLogger('gajim.c.commands')
-
-class AdHocCommand:
- commandnode = 'command'
- commandname = 'The Command'
- commandfeatures = (nbxmpp.NS_DATA,)
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- This returns True if that command should be visible and invokable for
- others
-
- samejid - True when command is invoked by an entity with the same bare
- 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.getTag('command', namespace=nbxmpp.NS_COMMANDS)
- cmd.setAttr('sessionid', self.sessionid)
- cmd.setAttr('node', self.commandnode)
- cmd.setAttr('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 badRequest(self, stanza):
- self.connection.connection.send(nbxmpp.Error(stanza,
- nbxmpp.NS_STANZAS + ' bad-request'))
-
- def cancel(self, request):
- response = self.buildResponse(request, status = 'canceled')[0]
- self.connection.connection.send(response)
- return False # finish the session
-
-class ChangeStatusCommand(AdHocCommand):
- commandnode = 'change-status'
- commandname = _('Change status information')
-
- def __init__(self, conn, jid, sessionid):
- AdHocCommand.__init__(self, conn, jid, sessionid)
- self.cb = self.first_step
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- return self.cb(request)
-
- def first_step(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions = ['execute'])
-
- cmd.addChild(node = dataforms.SimpleDataForm(
- title = _('Change status'),
- instructions = _('Set the presence type and description'),
- fields = [
- dataforms.Field('list-single',
- var = 'presence-type',
- label = 'Type of presence:',
- options = [
- ('chat', _('Free for chat')),
- ('online', _('Online')),
- ('away', _('Away')),
- ('xa', _('Extended away')),
- ('dnd', _('Do not disturb')),
- ('offline', _('Offline - disconnect'))],
- value = 'online',
- required = True),
- dataforms.Field('text-multi',
- var = 'presence-desc',
- label = _('Presence description:'))]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.cb = self.second_step
-
- return True # keep the session
-
- def second_step(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- presencetype = form['presence-type'].value
- if not presencetype in \
- ('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
- self.badRequest(request)
- return False
- except Exception: # KeyError if there's no presence-type field in form or
- # AttributeError if that field is of wrong type
- self.badRequest(request)
- return False
-
- try:
- presencedesc = form['presence-desc'].value
- except Exception: # same exceptions as in last comment
- presencedesc = ''
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = presencetype == 'offline')
-
- # send new status
- app.interface.roster.send_status(self.connection.name, presencetype,
- presencedesc)
-
- return False # finish the session
-
-def find_current_groupchats(account):
- from gajim import message_control
- rooms = []
- for gc_control in app.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + \
- app.interface.minimized_controls[account].values():
- acct = gc_control.account
- # check if account is the good one
- if acct != account:
- continue
- room_jid = gc_control.room_jid
- nick = gc_control.nick
- if room_jid in app.gc_connected[acct] and \
- app.gc_connected[acct][room_jid]:
- rooms.append((room_jid, nick,))
- return rooms
-
-
-class LeaveGroupchatsCommand(AdHocCommand):
- commandnode = 'leave-groupchats'
- commandname = _('Leave Groupchats')
-
- def __init__(self, conn, jid, sessionid):
- AdHocCommand.__init__(self, conn, jid, sessionid)
- self.cb = self.first_step
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Leave groupchats is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- return self.cb(request)
-
- def first_step(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions=['execute'])
- options = []
- account = self.connection.name
- for gc in find_current_groupchats(account):
- options.append(('%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
- {'nickname': gc[1], 'room_jid': gc[0]}))
- if not len(options):
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('You have not joined a groupchat.'))
-
- self.connection.connection.send(response)
- return False
-
- cmd.addChild(node=dataforms.SimpleDataForm(
- title = _('Leave Groupchats'),
- instructions = _('Choose the groupchats you want to leave'),
- fields=[
- dataforms.Field('list-multi',
- var = 'groupchats',
- label = _('Groupchats'),
- options = options,
- required = True)]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.cb = self.second_step
-
- return True # keep the session
-
- def second_step(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- gc = form['groupchats'].values
- except Exception: # KeyError if there's no groupchats in form
- self.badRequest(request)
- return False
- account = self.connection.name
- try:
- for room_jid in gc:
- gc_control = app.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if not gc_control:
- gc_control = app.interface.minimized_controls[account]\
- [room_jid]
- gc_control.shutdown()
- app.interface.roster.remove_groupchat(room_jid, account)
- continue
- gc_control.parent_win.remove_tab(gc_control, None, force = True)
- except Exception: # KeyError if there's no such room opened
- self.badRequest(request)
- return False
- response, cmd = self.buildResponse(request, status = 'completed')
- note = _('You left the following groupchats:')
- for room_jid in gc:
- note += '\n\t' + room_jid
- cmd.addChild('note', {}, note)
-
- self.connection.connection.send(response)
- return False
-
-
-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 = {}
- if app.config.get('remote_commands'):
- for cmdobj in (ChangeStatusCommand, LeaveGroupchatsCommand):
- self.__commands[cmdobj.commandnode] = cmdobj
-
- # a list of sessions; keys are tuples (jid, sessionid, node)
- self.__sessions = {}
-
- def getOurBareJID(self):
- return app.get_jid_from_account(self.name)
-
- def isSameJID(self, jid):
- """
- Test if the bare jid given is the same as our bare jid
- """
- return nbxmpp.JID(jid).getStripped() == self.getOurBareJID()
-
- def commandListQuery(self, con, iq_obj):
- iq = iq_obj.buildReply('result')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- q = iq.getTag('query')
- # buildReply don't copy the node attribute. Re-add it
- q.setAttr('node', nbxmpp.NS_COMMANDS)
-
- for node, cmd in self.__commands.items():
- if cmd.isVisibleFor(self.isSameJID(jid)):
- q.addChild('item', {
- # TODO: find the jid
- 'jid': self.getOurBareJID() + '/' + self.server_resource,
- 'node': node,
- 'name': cmd.commandname})
-
- self.connection.send(iq)
-
- def commandInfoQuery(self, con, iq_obj):
- """
- Send disco#info result for query for command (JEP-0050, example 6.).
- Return True if the result was sent, False if not
- """
- try:
- jid = helpers.get_full_jid_from_iq(iq_obj)
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % iq_obj.getFrom())
- return
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(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': nbxmpp.NS_COMMANDS})
- for feature in cmd.commandfeatures:
- q.addChild('feature', attrs = {'var': feature})
-
- self.connection.send(iq)
- return True
-
- return False
-
- def commandItemsQuery(self, con, iq_obj):
- """
- Send disco#items result for query for command. 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(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- 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
- # and command exist
- if node not in self.__commands.keys():
- self.connection.send(
- nbxmpp.Error(iq_obj, nbxmpp.NS_STANZAS + ' item-not-found'))
- raise nbxmpp.NodeProcessed
-
- newcmd = self.__commands[node]
- if not newcmd.isVisibleFor(self.isSameJID(jid)):
- return
-
- # generate new sessionid
- sessionid = self.connection.getAnID()
-
- # create new instance and run it
- obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
- rc = obj.execute(iq_obj)
- if rc:
- self.__sessions[(jid, sessionid, node)] = obj
- raise nbxmpp.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' or action is None:
- rc = obj.execute(iq_obj)
- elif action == 'complete':
- rc = obj.complete(iq_obj)
- else:
- # action is wrong. stop the session, send error
- raise AttributeError
- 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 nbxmpp.NodeProcessed
=====================================
gajim/common/connection_handlers.py
=====================================
--- a/gajim/common/connection_handlers.py
+++ b/gajim/common/connection_handlers.py
@@ -42,7 +42,6 @@ from gajim.common import helpers
from gajim.common import app
from gajim.common import jingle_xtls
from gajim.common.caps_cache import muc_caps_cache
-from gajim.common.commands import ConnectionCommands
from gajim.common.protocol.caps import ConnectionCaps
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
from gajim.common.protocol.bytestream import ConnectionIBBytestream
@@ -218,7 +217,7 @@ class ConnectionDisco:
if not self.connection or self.connected < 2:
return
- if self.commandItemsQuery(con, iq_obj):
+ if self.get_module('AdHocCommands').command_items_query(iq_obj):
raise nbxmpp.NodeProcessed
node = iq_obj.getTagAttr('query', 'node')
if node is None:
@@ -226,7 +225,7 @@ class ConnectionDisco:
self.connection.send(result)
raise nbxmpp.NodeProcessed
if node == nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
+ self.get_module('AdHocCommands').command_list_query(iq_obj)
raise nbxmpp.NodeProcessed
def _DiscoverInfoGetCB(self, con, iq_obj):
@@ -235,7 +234,7 @@ class ConnectionDisco:
return
node = iq_obj.getQuerynode()
- if self.commandInfoQuery(con, iq_obj):
+ if self.get_module('AdHocCommands').command_info_query(iq_obj):
raise nbxmpp.NodeProcessed
id_ = iq_obj.getAttr('id')
@@ -743,14 +742,12 @@ class ConnectionHandlersBase:
return sess
class ConnectionHandlers(ConnectionArchive313,
-ConnectionSocks5Bytestream, ConnectionDisco,
-ConnectionCommands, ConnectionCaps,
+ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCaps,
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
def __init__(self):
ConnectionArchive313.__init__(self)
ConnectionSocks5Bytestream.__init__(self)
ConnectionIBBytestream.__init__(self)
- ConnectionCommands.__init__(self)
# Handle presences BEFORE caps
app.nec.register_incoming_event(PresenceReceivedEvent)
@@ -1339,8 +1336,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
nbxmpp.NS_MUC_ADMIN)
con.RegisterHandler('iq', self._SecLabelCB, 'result',
nbxmpp.NS_SECLABEL_CATALOG)
- con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
- nbxmpp.NS_COMMANDS)
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
nbxmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
=====================================
gajim/common/modules/__init__.py
=====================================
--- a/gajim/common/modules/__init__.py
+++ b/gajim/common/modules/__init__.py
@@ -18,6 +18,8 @@ from pathlib import Path
log = logging.getLogger('gajim.c.m')
+ZEROCONF_MODULES = ['adhoc_commands']
+
imported_modules = []
_modules = {}
@@ -31,12 +33,26 @@ for file in Path(__file__).parent.iterdir():
if file.stem == 'pep':
# Register the PEP module first, because other modules
# depend on it
- imported_modules.insert(0, module)
+ imported_modules.insert(0, (module, file.stem))
else:
- imported_modules.append(module)
+ imported_modules.append((module, file.stem))
class ModuleMock:
+ def __init__(self, name):
+ self._name = name
+
+ # HTTPUpload
+ self.available = False
+
+ # Blocking
+ self.blocked = []
+
+ # Privacy Lists
+ self.blocked_contacts = []
+ self.blocked_groups = []
+ self.blocked_all = False
+
def __getattr__(self, key):
def _mock(self, *args, **kwargs):
return
@@ -48,7 +64,11 @@ def register(con, *args, **kwargs):
return
_modules[con.name] = {}
for module in imported_modules:
- instance, name = module.get_instance(con, *args, **kwargs)
+ mod, name = module
+ if con.name == 'Local':
+ if name not in ZEROCONF_MODULES:
+ continue
+ instance, name = mod.get_instance(con, *args, **kwargs)
_modules[con.name][name] = instance
@@ -60,7 +80,7 @@ def get(account, name):
try:
return _modules[account][name]
except KeyError:
- return ModuleMock()
+ return ModuleMock(name)
def get_handlers(con):
=====================================
gajim/common/modules/adhoc_commands.py
=====================================
--- /dev/null
+++ b/gajim/common/modules/adhoc_commands.py
@@ -0,0 +1,573 @@
+# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
+# Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
+# Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
+# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
+# Stephan Erb <steve-e AT h3c.de>
+# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
+#
+# This file is part of Gajim.
+#
+# Gajim 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 3 only.
+#
+# Gajim 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import nbxmpp
+
+from gajim.common import helpers
+from gajim.common import dataforms
+from gajim.common import app
+from gajim.common.nec import NetworkIncomingEvent
+
+log = logging.getLogger('gajim.c.m.commands')
+
+
+class AdHocCommand:
+ commandnode = 'command'
+ commandname = 'The Command'
+ commandfeatures = (nbxmpp.NS_DATA,)
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ This returns True if that command should be visible and invokable for
+ others
+
+ samejid - True when command is invoked by an entity with the same bare
+ 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.getTag('command', namespace=nbxmpp.NS_COMMANDS)
+ cmd.setAttr('sessionid', self.sessionid)
+ cmd.setAttr('node', self.commandnode)
+ cmd.setAttr('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 badRequest(self, stanza):
+ self.connection.connection.send(
+ nbxmpp.Error(stanza, nbxmpp.NS_STANZAS + ' bad-request'))
+
+ def cancel(self, request):
+ response = self.buildResponse(request, status='canceled')[0]
+ self.connection.connection.send(response)
+ return False # finish the session
+
+
+class ChangeStatusCommand(AdHocCommand):
+ commandnode = 'change-status'
+ commandname = _('Change status information')
+
+ def __init__(self, conn, jid, sessionid):
+ AdHocCommand.__init__(self, conn, jid, sessionid)
+ self.cb = self.first_step
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ Change status is visible only if the entity has the same bare jid
+ """
+ return samejid
+
+ def execute(self, request):
+ return self.cb(request)
+
+ def first_step(self, request):
+ # first query...
+ response, cmd = self.buildResponse(request,
+ defaultaction='execute',
+ actions=['execute'])
+
+ cmd.addChild(
+ node=dataforms.SimpleDataForm(
+ title=_('Change status'),
+ instructions=_('Set the presence type and description'),
+ fields=[
+ dataforms.Field(
+ 'list-single',
+ var='presence-type',
+ label='Type of presence:',
+ options=[
+ ('chat', _('Free for chat')),
+ ('online', _('Online')),
+ ('away', _('Away')),
+ ('xa', _('Extended away')),
+ ('dnd', _('Do not disturb')),
+ ('offline', _('Offline - disconnect'))],
+ value='online',
+ required=True),
+ dataforms.Field(
+ 'text-multi',
+ var='presence-desc',
+ label=_('Presence description:'))
+ ]
+ )
+ )
+
+ self.connection.connection.send(response)
+
+ # for next invocation
+ self.cb = self.second_step
+
+ return True # keep the session
+
+ def second_step(self, request):
+ # check if the data is correct
+ try:
+ form = dataforms.SimpleDataForm(
+ extend=request.getTag('command').getTag('x'))
+ except Exception:
+ self.badRequest(request)
+ return False
+
+ try:
+ presencetype = form['presence-type'].value
+ if presencetype not in ('chat', 'online', 'away',
+ 'xa', 'dnd', 'offline'):
+ self.badRequest(request)
+ return False
+ except Exception:
+ # KeyError if there's no presence-type field in form or
+ # AttributeError if that field is of wrong type
+ self.badRequest(request)
+ return False
+
+ try:
+ presencedesc = form['presence-desc'].value
+ except Exception: # same exceptions as in last comment
+ presencedesc = ''
+
+ response, cmd = self.buildResponse(request, status='completed')
+ cmd.addChild('note', {}, _('The status has been changed.'))
+
+ # if going offline, we need to push response so it won't go into
+ # queue and disappear
+ self.connection.connection.send(response,
+ now=presencetype == 'offline')
+
+ # send new status
+ app.interface.roster.send_status(
+ self.connection.name, presencetype, presencedesc)
+
+ return False # finish the session
+
+
+def find_current_groupchats(account):
+ from gajim import message_control
+ rooms = []
+ for gc_control in app.interface.msg_win_mgr.get_controls(
+ message_control.TYPE_GC) + \
+ app.interface.minimized_controls[account].values():
+ acct = gc_control.account
+ # check if account is the good one
+ if acct != account:
+ continue
+ room_jid = gc_control.room_jid
+ nick = gc_control.nick
+ if (room_jid in app.gc_connected[acct] and
+ app.gc_connected[acct][room_jid]):
+ rooms.append((room_jid, nick,))
+ return rooms
+
+
+class LeaveGroupchatsCommand(AdHocCommand):
+ commandnode = 'leave-groupchats'
+ commandname = _('Leave Groupchats')
+
+ def __init__(self, conn, jid, sessionid):
+ AdHocCommand.__init__(self, conn, jid, sessionid)
+ self.cb = self.first_step
+
+ @staticmethod
+ def isVisibleFor(samejid):
+ """
+ Leave groupchats is visible only if the entity has the same bare jid
+ """
+ return samejid
+
+ def execute(self, request):
+ return self.cb(request)
+
+ def first_step(self, request):
+ # first query...
+ response, cmd = self.buildResponse(request,
+ defaultaction='execute',
+ actions=['execute'])
+ options = []
+ account = self.connection.name
+ for gc in find_current_groupchats(account):
+ options.append(
+ ('%s' % gc[0],
+ _('%(nickname)s on %(room_jid)s') % {'nickname': gc[1],
+ 'room_jid': gc[0]}))
+ if not len(options):
+ response, cmd = self.buildResponse(request, status='completed')
+ cmd.addChild('note', {}, _('You have not joined a groupchat.'))
+
+ self.connection.connection.send(response)
+ return False
+
+ cmd.addChild(
+ node=dataforms.SimpleDataForm(
+ title=_('Leave Groupchats'),
+ instructions=_('Choose the groupchats you want to leave'),
+ fields=[
+ dataforms.Field(
+ 'list-multi',
+ var='groupchats',
+ label=_('Groupchats'),
+ options=options,
+ required=True)
+ ]
+ )
+ )
+
+ self.connection.connection.send(response)
+
+ # for next invocation
+ self.cb = self.second_step
+
+ return True # keep the session
+
+ def second_step(self, request):
+ # check if the data is correct
+ try:
+ form = dataforms.SimpleDataForm(
+ extend=request.getTag('command').getTag('x'))
+ except Exception:
+ self.badRequest(request)
+ return False
+
+ try:
+ gc = form['groupchats'].values
+ except Exception: # KeyError if there's no groupchats in form
+ self.badRequest(request)
+ return False
+ account = self.connection.name
+ try:
+ for room_jid in gc:
+ gc_control = app.interface.msg_win_mgr.get_gc_control(
+ room_jid, account)
+ if not gc_control:
+ gc_control = app.interface.minimized_controls[account][room_jid]
+ gc_control.shutdown()
+ app.interface.roster.remove_groupchat(room_jid, account)
+ continue
+ gc_control.parent_win.remove_tab(gc_control, None, force=True)
+ except Exception: # KeyError if there's no such room opened
+ self.badRequest(request)
+ return False
+ response, cmd = self.buildResponse(request, status='completed')
+ note = _('You left the following groupchats:')
+ for room_jid in gc:
+ note += '\n\t' + room_jid
+ cmd.addChild('note', {}, note)
+
+ self.connection.connection.send(response)
+ return False
+
+
+class AdHocCommands:
+ def __init__(self, con):
+ self._con = con
+ self._account = con.name
+
+ self.handlers = [
+ ('iq', self._execute_command_received, 'set', nbxmpp.NS_COMMANDS)
+ ]
+
+ # a list of all commands exposed: node -> command class
+ self._commands = {}
+ if app.config.get('remote_commands'):
+ for cmdobj in (ChangeStatusCommand, LeaveGroupchatsCommand):
+ self._commands[cmdobj.commandnode] = cmdobj
+
+ # a list of sessions; keys are tuples (jid, sessionid, node)
+ self._sessions = {}
+
+ def get_own_bare_jid(self):
+ return self._con.get_own_jid().getStripped()
+
+ def is_same_jid(self, jid):
+ """
+ Test if the bare jid given is the same as our bare jid
+ """
+ return nbxmpp.JID(jid).getStripped() == self.get_own_bare_jid()
+
+ def command_list_query(self, stanza):
+ iq = stanza.buildReply('result')
+ jid = helpers.get_full_jid_from_iq(stanza)
+ q = iq.getTag('query')
+ # buildReply don't copy the node attribute. Re-add it
+ q.setAttr('node', nbxmpp.NS_COMMANDS)
+
+ for node, cmd in self._commands.items():
+ if cmd.isVisibleFor(self.is_same_jid(jid)):
+ q.addChild('item', {
+ # TODO: find the jid
+ 'jid': str(self._con.get_own_jid()),
+ 'node': node,
+ 'name': cmd.commandname})
+
+ self._con.connection.send(iq)
+
+ def command_info_query(self, stanza):
+ """
+ Send disco#info result for query for command (XEP-0050, example 6.).
+ Return True if the result was sent, False if not
+ """
+ try:
+ jid = helpers.get_full_jid_from_iq(stanza)
+ except helpers.InvalidFormat:
+ log.warning('Invalid JID: %s, ignoring it' % stanza.getFrom())
+ return
+ node = stanza.getTagAttr('query', 'node')
+
+ if node not in self._commands:
+ return False
+
+ cmd = self._commands[node]
+ if cmd.isVisibleFor(self.is_same_jid(jid)):
+ iq = stanza.buildReply('result')
+ q = iq.getTag('query')
+ q.addChild('identity',
+ attrs={'type': 'command-node',
+ 'category': 'automation',
+ 'name': cmd.commandname})
+ q.addChild('feature', attrs={'var': nbxmpp.NS_COMMANDS})
+ for feature in cmd.commandfeatures:
+ q.addChild('feature', attrs={'var': feature})
+
+ self._con.connection.send(iq)
+ return True
+
+ return False
+
+ def command_items_query(self, stanza):
+ """
+ Send disco#items result for query for command.
+ Return True if the result was sent, False if not.
+ """
+ jid = helpers.get_full_jid_from_iq(stanza)
+ node = stanza.getTagAttr('query', 'node')
+
+ if node not in self._commands:
+ return False
+
+ cmd = self._commands[node]
+ if cmd.isVisibleFor(self.is_same_jid(jid)):
+ iq = stanza.buildReply('result')
+ self._con.connection.send(iq)
+ return True
+
+ return False
+
+ def _execute_command_received(self, con, stanza):
+ jid = helpers.get_full_jid_from_iq(stanza)
+
+ cmd = stanza.getTag('command')
+ if cmd is None:
+ log.error('Malformed stanza (no command node) %s', stanza)
+ raise nbxmpp.NodeProcessed
+
+ node = cmd.getAttr('node')
+ if node is None:
+ log.error('Malformed stanza (no node attr) %s', stanza)
+ raise nbxmpp.NodeProcessed
+
+ sessionid = cmd.getAttr('sessionid')
+ if sessionid is None:
+ # we start a new command session
+ # only if we are visible for the jid and command exist
+ if node not in self._commands.keys():
+ self._con.connection.send(
+ nbxmpp.Error(
+ stanza, nbxmpp.NS_STANZAS + ' item-not-found'))
+ log.warning('Comand %s does not exist: %s', node, jid)
+ raise nbxmpp.NodeProcessed
+
+ newcmd = self._commands[node]
+ if not newcmd.isVisibleFor(self.is_same_jid(jid)):
+ log.warning('Command not visible for jid: %s', jid)
+ raise nbxmpp.NodeProcessed
+
+ # generate new sessionid
+ sessionid = self._con.connection.getAnID()
+
+ # create new instance and run it
+ obj = newcmd(conn=self, jid=jid, sessionid=sessionid)
+ rc = obj.execute(stanza)
+ if rc:
+ self._sessions[(jid, sessionid, node)] = obj
+ log.info('Comand %s executed: %s', node, jid)
+ raise nbxmpp.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!
+ log.warning('Invalid session %s', magictuple)
+ raise nbxmpp.NodeProcessed
+
+ action = cmd.getAttr('action')
+ obj = self._sessions[magictuple]
+
+ try:
+ if action == 'cancel':
+ rc = obj.cancel(stanza)
+ elif action == 'prev':
+ rc = obj.prev(stanza)
+ elif action == 'next':
+ rc = obj.next(stanza)
+ elif action == 'execute' or action is None:
+ rc = obj.execute(stanza)
+ elif action == 'complete':
+ rc = obj.complete(stanza)
+ else:
+ # action is wrong. stop the session, send error
+ raise AttributeError
+ except AttributeError:
+ # the command probably doesn't handle invoked action...
+ # stop the session, return error
+ del self._sessions[magictuple]
+ log.warning('Wrong action %s %s', node, jid)
+ raise nbxmpp.NodeProcessed
+
+ # delete the session if rc is False
+ if not rc:
+ del self._sessions[magictuple]
+
+ raise nbxmpp.NodeProcessed
+
+ def request_command_list(self, jid):
+ """
+ Request the command list.
+ """
+ log.info('Request Command List: %s', jid)
+ query = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_ITEMS)
+ query.setQuerynode(nbxmpp.NS_COMMANDS)
+
+ self._con.connection.SendAndCallForResponse(
+ query, self._command_list_received)
+
+ def _command_list_received(self, stanza):
+ if not nbxmpp.isResultNode(stanza):
+ log.info('Error: %s', stanza.getError())
+
+ app.nec.push_incoming_event(
+ AdHocCommandError(None, conn=self._con,
+ error=stanza.getError()))
+ return
+
+ items = stanza.getQueryPayload()
+ commandlist = []
+ if items:
+ commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items]
+
+ log.info('Received: %s', commandlist)
+ app.nec.push_incoming_event(
+ AdHocCommandListReceived(
+ None, conn=self._con, commandlist=commandlist))
+
+ def send_command(self, jid, node, session_id,
+ form, action='execute'):
+ """
+ Send the command with data form. Wait for reply
+ """
+ log.info('Send Command: %s %s %s %s', jid, node, session_id, action)
+ stanza = nbxmpp.Iq(typ='set', to=jid)
+ cmdnode = stanza.addChild('command',
+ namespace=nbxmpp.NS_COMMANDS,
+ attrs={'node': node,
+ 'action': action})
+
+ if session_id:
+ cmdnode.setAttr('sessionid', session_id)
+
+ if form:
+ cmdnode.addChild(node=form.get_purged())
+
+ self._con.connection.SendAndCallForResponse(
+ stanza, self._action_response_received)
+
+ def _action_response_received(self, stanza):
+ if not nbxmpp.isResultNode(stanza):
+ log.info('Error: %s', stanza.getError())
+
+ app.nec.push_incoming_event(
+ AdHocCommandError(None, conn=self._con,
+ error=stanza.getError()))
+ return
+ log.info('Received action response')
+ command = stanza.getTag('command')
+ app.nec.push_incoming_event(
+ AdHocCommandActionResponse(
+ None, conn=self._con, command=command))
+
+ def send_cancel(self, jid, node, session_id):
+ """
+ Send the command with action='cancel'
+ """
+ log.info('Cancel: %s %s %s', jid, node, session_id)
+ stanza = nbxmpp.Iq(typ='set', to=jid)
+ stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
+ attrs={
+ 'node': node,
+ 'sessionid': session_id,
+ 'action': 'cancel'
+ })
+
+ self._con.connection.SendAndCallForResponse(
+ stanza, self._cancel_result_received)
+
+ def _cancel_result_received(self, stanza):
+ if not nbxmpp.isResultNode(stanza):
+ log.warning('Error: %s', stanza.getError())
+ else:
+ log.info('Cancel successful')
+
+
+class AdHocCommandError(NetworkIncomingEvent):
+ name = 'adhoc-command-error'
+ base_network_events = []
+
+
+class AdHocCommandListReceived(NetworkIncomingEvent):
+ name = 'adhoc-command-list'
+ base_network_events = []
+
+
+class AdHocCommandActionResponse(NetworkIncomingEvent):
+ name = 'adhoc-command-action-response'
+ base_network_events = []
+
+
+def get_instance(*args, **kwargs):
+ return AdHocCommands(*args, **kwargs), 'AdHocCommands'
=====================================
gajim/common/zeroconf/connection_handlers_zeroconf.py
=====================================
--- a/gajim/common/zeroconf/connection_handlers_zeroconf.py
+++ b/gajim/common/zeroconf/connection_handlers_zeroconf.py
@@ -26,7 +26,6 @@
import nbxmpp
from gajim.common import app
-from gajim.common.commands import ConnectionCommands
from gajim.common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
from gajim.common.connection_handlers_events import ZeroconfMessageReceivedEvent
@@ -49,13 +48,12 @@ class ConnectionVcard:
class ConnectionHandlersZeroconf(ConnectionVcard,
-ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
+ConnectionSocks5BytestreamZeroconf,
connection_handlers.ConnectionHandlersBase,
connection_handlers.ConnectionJingle):
def __init__(self):
ConnectionVcard.__init__(self)
ConnectionSocks5BytestreamZeroconf.__init__(self)
- ConnectionCommands.__init__(self)
connection_handlers.ConnectionJingle.__init__(self)
connection_handlers.ConnectionHandlersBase.__init__(self)
@@ -82,13 +80,13 @@ connection_handlers.ConnectionJingle):
if not self.connection or self.connected < 2:
return
- if self.commandItemsQuery(con, iq_obj):
+ if self.get_module('AdHocCommands').command_items_query(iq_obj):
raise nbxmpp.NodeProcessed
node = iq_obj.getTagAttr('query', 'node')
if node is None:
result = iq_obj.buildReply('result')
self.connection.send(result)
raise nbxmpp.NodeProcessed
- if node==nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
+ if node == nbxmpp.NS_COMMANDS:
+ self.get_module('AdHocCommands').command_list_query(iq_obj)
raise nbxmpp.NodeProcessed
=====================================
gajim/common/zeroconf/connection_zeroconf.py
=====================================
--- a/gajim/common/zeroconf/connection_zeroconf.py
+++ b/gajim/common/zeroconf/connection_zeroconf.py
@@ -47,6 +47,7 @@ from gi.repository import GLib
from gajim.common.connection import CommonConnection
from gajim.common import app
from gajim.common import ged
+from gajim.common import modules
from gajim.common.zeroconf import client_zeroconf
from gajim.common.zeroconf import zeroconf
from gajim.common.zeroconf.connection_handlers_zeroconf import *
@@ -69,6 +70,9 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
CommonConnection.__init__(self, name)
self.is_zeroconf = True
+ # Register all modules
+ modules.register(self)
+
app.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
self._nec_message_outgoing)
app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
View it on GitLab: https://dev.gajim.org/gajim/gajim/commit/a2d7283e6e334417cd89512b5ac5efdd94923975
--
View it on GitLab: https://dev.gajim.org/gajim/gajim/commit/a2d7283e6e334417cd89512b5ac5efdd94923975
You're receiving this email because of your account on dev.gajim.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.gajim.org/pipermail/commits/attachments/20180708/9e14d6a3/attachment-0001.html>
More information about the Commits
mailing list