[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