Mercurial > libervia-backend
annotate src/plugins/plugin_adhoc_dbus.py @ 1005:b4af31a8a4f2
core (logs): added formatting, name filter and outputs management:
- formatting is inspired from, and use when possible, standard logging. "message", "levelname", and "name" are the only format managed, depending on backend more can be managed (standard backend formats are specified in official python logging doc)
- name filter use regular expressions. It's possible to log only plugins with SAT_LOG_LOGGER="^sat.plugins". To log only XEPs 96 & 65, we can use SAT_LOG_LOGGER='(xep_0095|xep_0065)'
- output management use a particular syntax:
- output handler are name with "//", so far there are "//default" (most of time stderr), "//memory" and "//file"
- options can be specified in parenthesis, e.g. "//memory(50)" mean a 50 lines memory buffer (50 is the current default, so that's equivalent to "//memory")
- several handlers can be specified: "//file(/tmp/sat.log)//default" will use the default logging + a the /tmp/sat.log file
- if there is only one handler, it use the file handler: "/tmp/sat.log" is the same as "//file(/tmp/sat.log)"
- not finished, need more work for twisted and basic backends
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 05 May 2014 18:58:34 +0200 |
parents | 301b342c697a |
children | 069ad98b360d |
rev | line source |
---|---|
822 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for adding D-Bus to Ad-Hoc Commands | |
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | |
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 | |
20 from sat.core.i18n import _, D_ | |
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__) |
822 | 24 from twisted.words.protocols.jabber import jid |
25 from twisted.internet import defer, reactor | |
26 from wokkel import data_form | |
27 from lxml import etree | |
28 from os import path | |
29 import uuid | |
30 import dbus | |
31 from dbus.mainloop.glib import DBusGMainLoop | |
32 DBusGMainLoop(set_as_default=True) | |
33 | |
34 FD_NAME = "org.freedesktop.DBus" | |
35 FD_PATH = "/org/freedekstop/DBus" | |
36 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" | |
37 | |
38 INTROSPECT_METHOD = "Introspect" | |
39 IGNORED_IFACES_START = ('org.freedesktop', 'org.qtproject', 'org.kde.KMainWindow') # commands in interface starting with these values will be ignored | |
40 FLAG_LOOP = 'LOOP' | |
41 | |
42 PLUGIN_INFO = { | |
43 "name": "Ad-Hoc Commands - D-Bus", | |
44 "import_name": "AD_HOC_DBUS", | |
45 "type": "Misc", | |
46 "protocols": [], | |
47 "dependencies": ["XEP-0050"], | |
48 "main": "AdHocDBus", | |
49 "handler": "no", | |
50 "description": _("""Add D-Bus management to Ad-Hoc commands""") | |
51 } | |
52 | |
53 | |
54 class AdHocDBus(object): | |
55 | |
56 def __init__(self, host): | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
57 log.info(_("plugin Ad-Hoc D-Bus initialization")) |
822 | 58 self.host = host |
59 host.bridge.addMethod("adHocDBusAddAuto", ".plugin", in_sign='sasasasasasass', out_sign='(sa(sss))', | |
60 method=self._adHocDBusAddAuto, | |
61 async=True) | |
62 self.session_bus = dbus.SessionBus() | |
63 self.fd_object = self.session_bus.get_object(FD_NAME, FD_PATH, introspect=False) | |
64 self.XEP_0050 = host.plugins['XEP-0050'] | |
65 | |
66 def _DBusAsyncCall(self, proxy, method, *args, **kwargs): | |
67 """ Call a DBus method asynchronously and return a deferred | |
68 @param proxy: DBus object proxy, as returner by get_object | |
69 @param method: name of the method to call | |
70 @param args: will be transmitted to the method | |
71 @param kwargs: will be transmetted to the method, except for the following poped values: | |
72 - interface: name of the interface to use | |
73 @return: a deferred | |
74 | |
75 """ | |
76 d = defer.Deferred() | |
77 interface = kwargs.pop('interface', None) | |
78 kwargs['reply_handler'] = lambda ret=None: d.callback(ret) | |
79 kwargs['error_handler'] = d.errback | |
80 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) | |
81 return d | |
82 | |
83 def _DBusListNames(self): | |
84 return self._DBusAsyncCall(self.fd_object, "ListNames") | |
85 | |
86 def _DBusIntrospect(self, proxy): | |
87 return self._DBusAsyncCall(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) | |
88 | |
89 def _acceptMethod(self, method): | |
90 """ Return True if we accept the method for a command | |
91 @param method: etree.Element | |
92 @return: True if the method is acceptable | |
93 | |
94 """ | |
95 if method.xpath("arg[@direction='in']"): # we don't accept method with argument for the moment | |
96 return False | |
97 return True | |
98 | |
99 @defer.inlineCallbacks | |
100 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
|
101 log.debug("introspecting path [%s]" % proxy.object_path) |
822 | 102 introspect_xml = yield self._DBusIntrospect(proxy) |
103 el = etree.fromstring(introspect_xml) | |
104 for node in el.iterchildren('node', 'interface'): | |
105 if node.tag == 'node': | |
106 new_path = path.join(proxy.object_path, node.get('name')) | |
107 new_proxy = self.session_bus.get_object(bus_name, new_path, introspect=False) | |
108 yield self._introspect(methods, bus_name, new_proxy) | |
109 elif node.tag == 'interface': | |
110 name = node.get('name') | |
111 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
|
112 log.debug('interface [%s] is ignored' % name) |
822 | 113 continue |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
114 log.debug("introspecting interface [%s]" % name) |
822 | 115 for method in node.iterchildren('method'): |
116 if self._acceptMethod(method): | |
117 method_name = method.get('name') | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
118 log.debug("method accepted: [%s]" % method_name) |
822 | 119 methods.add((proxy.object_path, name, method_name)) |
120 | |
121 def _adHocDBusAddAuto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key): | |
122 return self.adHocDBusAddAuto(prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key) | |
123 | |
124 @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
|
125 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 | 126 bus_names = yield self._DBusListNames() |
127 bus_names = [bus_name for bus_name in bus_names if '.' + prog_name in bus_name] | |
128 if not bus_names: | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
129 log.info("Can't find any bus for [%s]" % prog_name) |
822 | 130 return |
131 bus_names.sort() | |
132 for bus_name in bus_names: | |
133 if bus_name.endswith(prog_name): | |
134 break | |
993
301b342c697a
core: use of the new core.log module:
Goffi <goffi@goffi.org>
parents:
916
diff
changeset
|
135 log.info("bus name found: [%s]" % bus_name) |
822 | 136 proxy = self.session_bus.get_object(bus_name, '/', introspect=False) |
137 methods = set() | |
138 | |
139 yield self._introspect(methods, bus_name, proxy) | |
140 | |
141 if methods: | |
142 self._addCommand(prog_name, bus_name, methods, | |
143 allowed_jids = allowed_jids, | |
144 allowed_groups = allowed_groups, | |
145 allowed_magics = allowed_magics, | |
146 forbidden_jids = forbidden_jids, | |
147 forbidden_groups = forbidden_groups, | |
148 flags = flags, | |
149 profile_key = profile_key) | |
150 | |
151 defer.returnValue((bus_name, methods)) | |
152 | |
153 | |
916
1a759096ccbd
core: use of Const for profile_key + replaced '@DEFAULT@' default profile_key by '@NONE@'
Goffi <goffi@goffi.org>
parents:
822
diff
changeset
|
154 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 | 155 if flags is None: |
156 flags = set() | |
157 | |
158 def DBusCallback(command_elt, session_data, action, node, profile): | |
159 actions = session_data.setdefault('actions',[]) | |
160 names_map = session_data.setdefault('names_map', {}) | |
161 actions.append(action) | |
162 | |
163 if len(actions) == 1: | |
164 # it's our first request, we ask the desired new status | |
165 status = self.XEP_0050.STATUS.EXECUTING | |
166 form = data_form.Form('form', title=_('Command selection')) | |
167 options = [] | |
168 for path, iface, command in methods: | |
169 label = command.rsplit('.',1)[-1] | |
170 name = str(uuid.uuid4()) | |
171 names_map[name] = (path, iface, command) | |
172 options.append(data_form.Option(name, label)) | |
173 | |
174 field = data_form.Field('list-single', 'command', options=options, required=True) | |
175 form.addField(field) | |
176 | |
177 payload = form.toElement() | |
178 note = None | |
179 | |
180 elif len(actions) == 2: | |
181 # we should have the answer here | |
182 try: | |
183 x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next() | |
184 answer_form = data_form.Form.fromElement(x_elt) | |
185 command = answer_form['command'] | |
186 except KeyError, StopIteration: | |
187 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) | |
188 | |
189 if command not in names_map: | |
190 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) | |
191 | |
192 path, iface, command = names_map[command] | |
193 proxy = self.session_bus.get_object(bus_name, path) | |
194 | |
195 d = self._DBusAsyncCall(proxy, command, interface=iface) | |
196 | |
197 # job done, we can end the session, except if we have FLAG_LOOP | |
198 if FLAG_LOOP in flags: | |
199 # 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) | |
200 del actions[:] | |
201 names_map.clear() | |
202 return DBusCallback(None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile) | |
203 form = data_form.Form('form', title=_(u'Updated')) | |
204 form.addField(data_form.Field('fixed', u'Command sent')) | |
205 status = self.XEP_0050.STATUS.COMPLETED | |
206 payload = None | |
207 note = (self.XEP_0050.NOTE.INFO, _(u"Command sent")) | |
208 else: | |
209 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.INTERNAL) | |
210 | |
211 return (payload, status, None, note) | |
212 | |
213 self.XEP_0050.addAdHocCommand(DBusCallback, adhoc_name, | |
214 allowed_jids = allowed_jids, | |
215 allowed_groups = allowed_groups, | |
216 allowed_magics = allowed_magics, | |
217 forbidden_jids = forbidden_jids, | |
218 forbidden_groups = forbidden_groups, | |
219 profile_key = profile_key) |