Mercurial > libervia-backend
annotate src/plugins/plugin_adhoc_dbus.py @ 2309:c7a72b75232b
jp (shell): shell command (REPL mode), first draft:
This command launch jp in REPL mode, allowing do normal jp commands with some facilities.
Command can be selected with "cmd" (e.g. "cmd blog").
An argument can be fixed with "use" (e.g. "use output fancy").
Command is launched with "do", or directly with its name if it doesn't conflict with a shell command.
Arguments completion is still TODO (only shell commands are completed so far).
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 06 Jul 2017 20:28:25 +0200 |
parents | 33c8c4973743 |
children | 8b37a62336c3 |
rev | line source |
---|---|
1934
2daf7b4c6756
use of /usr/bin/env instead of /usr/bin/python in shebang
Goffi <goffi@goffi.org>
parents:
1933
diff
changeset
|
1 #!/usr/bin/env python2 |
822 | 2 # -*- coding: utf-8 -*- |
3 | |
4 # SAT plugin for adding D-Bus to Ad-Hoc Commands | |
1766 | 5 # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org) |
822 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
1540
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
20 from sat.core.i18n import _ |
916
1a759096ccbd
core: use of Const for profile_key + replaced '@DEFAULT@' default profile_key by '@NONE@'
Goffi <goffi@goffi.org>
parents:
822
diff
changeset
|
21 from sat.core.constants import Const as C |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
22 from sat.core.log import getLogger |
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
23 log = getLogger(__name__) |
1540
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
24 from sat.core import exceptions |
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
25 from twisted.internet import defer |
822 | 26 from wokkel import data_form |
1542
94901070478e
plugins: added new MissingModule exceptions to plugins using third party modules
Goffi <goffi@goffi.org>
parents:
1540
diff
changeset
|
27 try: |
94901070478e
plugins: added new MissingModule exceptions to plugins using third party modules
Goffi <goffi@goffi.org>
parents:
1540
diff
changeset
|
28 from lxml import etree |
94901070478e
plugins: added new MissingModule exceptions to plugins using third party modules
Goffi <goffi@goffi.org>
parents:
1540
diff
changeset
|
29 except ImportError: |
94901070478e
plugins: added new MissingModule exceptions to plugins using third party modules
Goffi <goffi@goffi.org>
parents:
1540
diff
changeset
|
30 raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/") |
94901070478e
plugins: added new MissingModule exceptions to plugins using third party modules
Goffi <goffi@goffi.org>
parents:
1540
diff
changeset
|
31 import os.path |
822 | 32 import uuid |
33 import dbus | |
34 from dbus.mainloop.glib import DBusGMainLoop | |
35 DBusGMainLoop(set_as_default=True) | |
36 | |
37 FD_NAME = "org.freedesktop.DBus" | |
38 FD_PATH = "/org/freedekstop/DBus" | |
39 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" | |
40 | |
41 INTROSPECT_METHOD = "Introspect" | |
42 IGNORED_IFACES_START = ('org.freedesktop', 'org.qtproject', 'org.kde.KMainWindow') # commands in interface starting with these values will be ignored | |
43 FLAG_LOOP = 'LOOP' | |
44 | |
45 PLUGIN_INFO = { | |
2145
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
46 C.PI_NAME: "Ad-Hoc Commands - D-Bus", |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
47 C.PI_IMPORT_NAME: "AD_HOC_DBUS", |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
48 C.PI_TYPE: "Misc", |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
49 C.PI_PROTOCOLS: [], |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
50 C.PI_DEPENDENCIES: ["XEP-0050"], |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
51 C.PI_MAIN: "AdHocDBus", |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
52 C.PI_HANDLER: "no", |
33c8c4973743
core (plugins): added missing contants + use of new constants in PLUGIN_INFO
Goffi <goffi@goffi.org>
parents:
1934
diff
changeset
|
53 C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands""") |
822 | 54 } |
55 | |
56 | |
57 class AdHocDBus(object): | |
58 | |
59 def __init__(self, host): | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
60 log.info(_("plugin Ad-Hoc D-Bus initialization")) |
822 | 61 self.host = host |
62 host.bridge.addMethod("adHocDBusAddAuto", ".plugin", in_sign='sasasasasasass', out_sign='(sa(sss))', | |
63 method=self._adHocDBusAddAuto, | |
64 async=True) | |
65 self.session_bus = dbus.SessionBus() | |
66 self.fd_object = self.session_bus.get_object(FD_NAME, FD_PATH, introspect=False) | |
67 self.XEP_0050 = host.plugins['XEP-0050'] | |
68 | |
69 def _DBusAsyncCall(self, proxy, method, *args, **kwargs): | |
70 """ Call a DBus method asynchronously and return a deferred | |
71 @param proxy: DBus object proxy, as returner by get_object | |
72 @param method: name of the method to call | |
73 @param args: will be transmitted to the method | |
74 @param kwargs: will be transmetted to the method, except for the following poped values: | |
75 - interface: name of the interface to use | |
76 @return: a deferred | |
77 | |
78 """ | |
79 d = defer.Deferred() | |
80 interface = kwargs.pop('interface', None) | |
81 kwargs['reply_handler'] = lambda ret=None: d.callback(ret) | |
82 kwargs['error_handler'] = d.errback | |
83 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) | |
84 return d | |
85 | |
86 def _DBusListNames(self): | |
87 return self._DBusAsyncCall(self.fd_object, "ListNames") | |
88 | |
89 def _DBusIntrospect(self, proxy): | |
90 return self._DBusAsyncCall(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) | |
91 | |
92 def _acceptMethod(self, method): | |
93 """ Return True if we accept the method for a command | |
94 @param method: etree.Element | |
95 @return: True if the method is acceptable | |
96 | |
97 """ | |
98 if method.xpath("arg[@direction='in']"): # we don't accept method with argument for the moment | |
99 return False | |
100 return True | |
101 | |
102 @defer.inlineCallbacks | |
103 def _introspect(self, methods, bus_name, proxy): | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
104 log.debug("introspecting path [%s]" % proxy.object_path) |
822 | 105 introspect_xml = yield self._DBusIntrospect(proxy) |
106 el = etree.fromstring(introspect_xml) | |
107 for node in el.iterchildren('node', 'interface'): | |
108 if node.tag == 'node': | |
1540
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
109 new_path = os.path.join(proxy.object_path, node.get('name')) |
822 | 110 new_proxy = self.session_bus.get_object(bus_name, new_path, introspect=False) |
111 yield self._introspect(methods, bus_name, new_proxy) | |
112 elif node.tag == 'interface': | |
113 name = node.get('name') | |
114 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START): | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
115 log.debug('interface [%s] is ignored' % name) |
822 | 116 continue |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
117 log.debug("introspecting interface [%s]" % name) |
822 | 118 for method in node.iterchildren('method'): |
119 if self._acceptMethod(method): | |
120 method_name = method.get('name') | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
121 log.debug("method accepted: [%s]" % method_name) |
822 | 122 methods.add((proxy.object_path, name, method_name)) |
123 | |
124 def _adHocDBusAddAuto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key): | |
125 return self.adHocDBusAddAuto(prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key) | |
126 | |
127 @defer.inlineCallbacks | |
916
1a759096ccbd
core: use of Const for profile_key + replaced '@DEFAULT@' default profile_key by '@NONE@'
Goffi <goffi@goffi.org>
parents:
822
diff
changeset
|
128 def adHocDBusAddAuto(self, prog_name, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE): |
822 | 129 bus_names = yield self._DBusListNames() |
130 bus_names = [bus_name for bus_name in bus_names if '.' + prog_name in bus_name] | |
131 if not bus_names: | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
132 log.info("Can't find any bus for [%s]" % prog_name) |
1933
16e65f15f31f
plugin adhoc_dbus, jp (cmd/adhoc): fixes adHocDBusAddAuto when no bus is found
souliane <souliane@mailoo.org>
parents:
1766
diff
changeset
|
133 defer.returnValue(("", [])) |
822 | 134 bus_names.sort() |
135 for bus_name in bus_names: | |
136 if bus_name.endswith(prog_name): | |
137 break | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
138 log.info("bus name found: [%s]" % bus_name) |
822 | 139 proxy = self.session_bus.get_object(bus_name, '/', introspect=False) |
140 methods = set() | |
141 | |
142 yield self._introspect(methods, bus_name, proxy) | |
143 | |
144 if methods: | |
145 self._addCommand(prog_name, bus_name, methods, | |
146 allowed_jids = allowed_jids, | |
147 allowed_groups = allowed_groups, | |
148 allowed_magics = allowed_magics, | |
149 forbidden_jids = forbidden_jids, | |
150 forbidden_groups = forbidden_groups, | |
151 flags = flags, | |
152 profile_key = profile_key) | |
153 | |
154 defer.returnValue((bus_name, methods)) | |
155 | |
156 | |
916
1a759096ccbd
core: use of Const for profile_key + replaced '@DEFAULT@' default profile_key by '@NONE@'
Goffi <goffi@goffi.org>
parents:
822
diff
changeset
|
157 def _addCommand(self, adhoc_name, bus_name, methods, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE): |
822 | 158 if flags is None: |
159 flags = set() | |
160 | |
161 def DBusCallback(command_elt, session_data, action, node, profile): | |
162 actions = session_data.setdefault('actions',[]) | |
163 names_map = session_data.setdefault('names_map', {}) | |
164 actions.append(action) | |
165 | |
166 if len(actions) == 1: | |
167 # it's our first request, we ask the desired new status | |
168 status = self.XEP_0050.STATUS.EXECUTING | |
169 form = data_form.Form('form', title=_('Command selection')) | |
170 options = [] | |
171 for path, iface, command in methods: | |
172 label = command.rsplit('.',1)[-1] | |
173 name = str(uuid.uuid4()) | |
174 names_map[name] = (path, iface, command) | |
175 options.append(data_form.Option(name, label)) | |
176 | |
177 field = data_form.Field('list-single', 'command', options=options, required=True) | |
178 form.addField(field) | |
179 | |
180 payload = form.toElement() | |
181 note = None | |
182 | |
183 elif len(actions) == 2: | |
184 # we should have the answer here | |
185 try: | |
186 x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() | |
187 answer_form = data_form.Form.fromElement(x_elt) | |
188 command = answer_form['command'] | |
1540
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
189 except (KeyError, StopIteration): |
822 | 190 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) |
191 | |
192 if command not in names_map: | |
193 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) | |
194 | |
195 path, iface, command = names_map[command] | |
196 proxy = self.session_bus.get_object(bus_name, path) | |
197 | |
1540
9a4e95c62380
plugin ad-hoc D-Bus: exception fixe + minor fixes
Goffi <goffi@goffi.org>
parents:
1396
diff
changeset
|
198 self._DBusAsyncCall(proxy, command, interface=iface) |
822 | 199 |
200 # job done, we can end the session, except if we have FLAG_LOOP | |
201 if FLAG_LOOP in flags: | |
202 # We have a loop, so we clear everything and we execute again the command as we had a first call (command_elt is not used, so None is OK) | |
203 del actions[:] | |
204 names_map.clear() | |
205 return DBusCallback(None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile) | |
206 form = data_form.Form('form', title=_(u'Updated')) | |
207 form.addField(data_form.Field('fixed', u'Command sent')) | |
208 status = self.XEP_0050.STATUS.COMPLETED | |
209 payload = None | |
210 note = (self.XEP_0050.NOTE.INFO, _(u"Command sent")) | |
211 else: | |
212 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.INTERNAL) | |
213 | |
214 return (payload, status, None, note) | |
215 | |
216 self.XEP_0050.addAdHocCommand(DBusCallback, adhoc_name, | |
217 allowed_jids = allowed_jids, | |
218 allowed_groups = allowed_groups, | |
219 allowed_magics = allowed_magics, | |
220 forbidden_jids = forbidden_jids, | |
221 forbidden_groups = forbidden_groups, | |
222 profile_key = profile_key) |