Mercurial > libervia-backend
comparison src/plugins/plugin_exp_events.py @ 2243:5e12fc5ae52a
plugin events: separation of event node and invitees node
- event node is handling the main metadata of the event, and invitees node handle the invitations/invitees answers
- invitees and blog node are automatically created and associated to the event, except if they are specified (in which cas the existing one are used and attached to the event node)
- extra metadata are added to <meta> elements
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 19 May 2017 12:43:41 +0200 |
parents | 230fc5b609a8 |
children | e8641b7718dc |
comparison
equal
deleted
inserted
replaced
2242:e5e54ff0b775 | 2243:5e12fc5ae52a |
---|---|
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 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/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _ |
21 from sat.core import exceptions | |
21 from sat.core.constants import Const as C | 22 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 23 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 24 log = getLogger(__name__) |
25 from sat.tools import utils | |
26 from sat.tools.common import uri as uri_parse | |
24 from twisted.internet import defer | 27 from twisted.internet import defer |
25 from twisted.words.protocols.jabber import jid | 28 from twisted.words.protocols.jabber import jid, error |
26 from twisted.words.xish import domish | 29 from twisted.words.xish import domish |
27 from wokkel import pubsub | 30 from wokkel import pubsub |
28 | 31 |
29 | 32 |
30 PLUGIN_INFO = { | 33 PLUGIN_INFO = { |
47 def __init__(self, host): | 50 def __init__(self, host): |
48 log.info(_(u"Event plugin initialization")) | 51 log.info(_(u"Event plugin initialization")) |
49 self.host = host | 52 self.host = host |
50 self._p = self.host.plugins["XEP-0060"] | 53 self._p = self.host.plugins["XEP-0060"] |
51 host.bridge.addMethod("eventGet", ".plugin", | 54 host.bridge.addMethod("eventGet", ".plugin", |
55 in_sign='ssss', out_sign='(ia{ss})', | |
56 method=self._eventGet, | |
57 async=True) | |
58 host.bridge.addMethod("eventCreate", ".plugin", | |
59 in_sign='ia{ss}ssss', out_sign='s', | |
60 method=self._eventCreate, | |
61 async=True) | |
62 host.bridge.addMethod("eventModify", ".plugin", | |
63 in_sign='sssia{ss}s', out_sign='', | |
64 method=self._eventModify, | |
65 async=True) | |
66 host.bridge.addMethod("eventInviteeGet", ".plugin", | |
52 in_sign='sss', out_sign='a{ss}', | 67 in_sign='sss', out_sign='a{ss}', |
53 method=self._eventGet, | 68 method=self._eventInviteeGet, |
54 async=True) | 69 async=True) |
55 host.bridge.addMethod("eventSet", ".plugin", | 70 host.bridge.addMethod("eventInviteeSet", ".plugin", |
56 in_sign='ssa{ss}s', out_sign='', | 71 in_sign='ssa{ss}s', out_sign='', |
57 method=self._eventSet, | 72 method=self._eventInviteeSet, |
58 async=True) | 73 async=True) |
59 | 74 |
60 def _eventGet(self, service, node, profile_key): | 75 def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): |
61 service = jid.JID(service) if service else None | 76 service = jid.JID(service) if service else None |
62 node = node if node else NS_EVENT | 77 node = node if node else NS_EVENT |
63 client = self.host.getClient(profile_key) | 78 client = self.host.getClient(profile_key) |
64 return self.eventGet(client, service, node) | 79 return self.eventGet(client, service, node, id_) |
65 | 80 |
66 @defer.inlineCallbacks | 81 @defer.inlineCallbacks |
67 def eventGet(self, client, service, node): | 82 def eventGet(self, client, service, node, id_=NS_EVENT): |
83 """Retrieve event data | |
84 | |
85 @param service(unicode, None): PubSub service | |
86 @param node(unicode): PubSub node of the event | |
87 @param id_(unicode): id_ with even data | |
88 @return (tuple[int, dict[unicode, unicode]): event data: | |
89 - timestamp of the event | |
90 - event metadata where key can be: | |
91 location: location of the event | |
92 image: URL of a picture to use to represent event | |
93 background-image: URL of a picture to use in background | |
94 an empty dict is returned if nothing has been answered yed | |
95 """ | |
96 if not id_: | |
97 id_ = NS_EVENT | |
98 items, metadata = yield self._p.getItems(service, node, item_ids=[id_], profile_key=client.profile) | |
99 try: | |
100 event_elt = next(items[0].elements(NS_EVENT, u'event')) | |
101 except IndexError: | |
102 raise exceptions.NotFound(_(u"No event with this id has been found")) | |
103 | |
104 try: | |
105 timestamp = utils.date_parse(next(event_elt.elements(NS_EVENT, "date"))) | |
106 except StopIteration: | |
107 timestamp = -1 | |
108 | |
109 data = {} | |
110 | |
111 for key in (u'name',): | |
112 try: | |
113 data[key] = event_elt[key] | |
114 except KeyError: | |
115 continue | |
116 | |
117 for elt_name in (u'description',): | |
118 try: | |
119 elt = next(event_elt.elements(NS_EVENT, elt_name)) | |
120 except StopIteration: | |
121 continue | |
122 else: | |
123 data[elt_name] = unicode(elt) | |
124 | |
125 for elt_name in (u'image', 'background-image'): | |
126 try: | |
127 image_elt = next(event_elt.elements(NS_EVENT, elt_name)) | |
128 data[elt_name] = image_elt['src'] | |
129 except StopIteration: | |
130 continue | |
131 except KeyError: | |
132 log.warning(_(u'no src found for image')) | |
133 | |
134 for uri_type in (u'invitees', u'blog'): | |
135 try: | |
136 elt = next(event_elt.elements(NS_EVENT, 'invitees')) | |
137 uri = data[uri_type + u'_uri'] = elt['uri'] | |
138 uri_data = uri_parse.parseXMPPUri(uri) | |
139 if uri_data[u'type'] != u'pubsub': | |
140 raise ValueError | |
141 except StopIteration: | |
142 log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type)) | |
143 except KeyError: | |
144 log.warning(_(u"incomplete {uri_type} element").format(uri_type=uri_type)) | |
145 except ValueError: | |
146 log.warning(_(u"bad {uri_type} element").format(uri_type=uri_type)) | |
147 else: | |
148 data[uri_type + u'_service'] = uri_data[u'path'] | |
149 data[uri_type + u'_node'] = uri_data[u'node'] | |
150 | |
151 for meta_elt in event_elt.elements(NS_EVENT, 'meta'): | |
152 key = meta_elt[u'name'] | |
153 if key in data: | |
154 log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml())) | |
155 continue | |
156 data[key] = unicode(meta_elt) | |
157 | |
158 defer.returnValue((timestamp, data)) | |
159 | |
160 def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): | |
161 service = jid.JID(service) if service else None | |
162 node = node if node else NS_EVENT | |
163 client = self.host.getClient(profile_key) | |
164 return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT) | |
165 | |
166 @defer.inlineCallbacks | |
167 def eventCreate(self, client, timestamp, data, service, node=None, item_id=NS_EVENT): | |
168 """Create or replace an event | |
169 | |
170 @param service(jid.JID, None): PubSub service | |
171 @param node(unicode, None): PubSub node of the event | |
172 None will create instant node. | |
173 @param item_id(unicode): ID of the item to create. | |
174 @param timestamp(timestamp, None) | |
175 @param data(dict[unicode, unicode]): data to update | |
176 dict will be cleared, do a copy if data are still needed | |
177 key can be: | |
178 - name: name of the event | |
179 - description: details | |
180 - image: main picture of the event | |
181 - background-image: image to use as background | |
182 @return (unicode): created node | |
183 """ | |
184 if not item_id: | |
185 raise ValueError(_(u"item_id must be set")) | |
186 if not service: | |
187 service = client.jid.userhostJID() | |
188 event_elt = domish.Element((NS_EVENT, 'event')) | |
189 if timestamp is not None and timestamp != -1: | |
190 formatted_date = utils.xmpp_date(timestamp) | |
191 event_elt.addElement((NS_EVENT, 'date'), content=formatted_date) | |
192 for key in (u'name',): | |
193 if key in data: | |
194 event_elt[key] = data.pop(key) | |
195 for key in (u'description',): | |
196 if key in data: | |
197 event_elt.addElement((NS_EVENT, key), content=data.pop(key)) | |
198 for key in (u'image', u'background-image'): | |
199 if key in data: | |
200 elt = event_elt.addElement((NS_EVENT, key)) | |
201 elt['src'] = data.pop(key) | |
202 | |
203 # we first create the invitees and blog nodes (if not specified in data) | |
204 for uri_type in (u'invitees', u'blog'): | |
205 key = uri_type + u'_uri' | |
206 if key not in data: | |
207 # FIXME: affiliate invitees | |
208 uri_node = yield self._p.createNode(client, service) | |
209 yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}) | |
210 uri_service = service | |
211 else: | |
212 # we suppose that *_service and *_node are present | |
213 # FIXME: handle cases when they are not | |
214 uri_service = data.pop(uri_type + u'_service') | |
215 uri_node = data.pop(uri_type + u'_node') | |
216 del data[key] | |
217 | |
218 elt = event_elt.addElement((NS_EVENT, uri_type)) | |
219 elt['uri'] = uri_parse.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node) | |
220 | |
221 # remaining data are put in <meta> elements | |
222 for key in data.keys(): | |
223 elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key)) | |
224 elt['name'] = key | |
225 | |
226 item_elt = pubsub.Item(id=item_id, payload=event_elt) | |
227 try: | |
228 # TODO: check auto-create, no need to create node first if available | |
229 node = yield self._p.createNode(client, service, nodeIdentifier=node) | |
230 except error.StanzaError as e: | |
231 if e.condition == u'conflict': | |
232 log.debug(_(u"requested node already exists")) | |
233 | |
234 yield self._p.publish(service, node, items=[item_elt], profile_key=client.profile) | |
235 | |
236 defer.returnValue(node) | |
237 | |
238 def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE): | |
239 service = jid.JID(service) if service else None | |
240 node = node if node else NS_EVENT | |
241 client = self.host.getClient(profile_key) | |
242 return self.eventModify(client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update) | |
243 | |
244 @defer.inlineCallbacks | |
245 def eventModify(self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None): | |
246 """Update an event | |
247 | |
248 Similar as create instead that it update existing item instead of | |
249 creating or replacing it. Params are the same as for [eventCreate]. | |
250 """ | |
251 event_timestamp, event_metadata = yield self.eventGet(client, service, node, id_) | |
252 new_timestamp = event_timestamp if timestamp_update is None else timestamp_update | |
253 new_data = event_metadata | |
254 if data_update: | |
255 for k, v in data_update.iteritems(): | |
256 new_data[k] = v | |
257 yield self.eventCreate(client, new_timestamp, new_data, service, node, id_) | |
258 | |
259 def _eventInviteeGet(self, service, node, profile_key): | |
260 service = jid.JID(service) if service else None | |
261 node = node if node else NS_EVENT | |
262 client = self.host.getClient(profile_key) | |
263 return self.eventInviteeGet(client, service, node) | |
264 | |
265 @defer.inlineCallbacks | |
266 def eventInviteeGet(self, client, service, node): | |
68 """Retrieve attendance from event node | 267 """Retrieve attendance from event node |
69 | 268 |
70 @param service(unicode, None): PubSub service | 269 @param service(unicode, None): PubSub service |
71 @param node(unicode): PubSub node of the event | 270 @param node(unicode): PubSub node of the event |
72 @return (dict): a dict with current attendance status, | 271 @return (dict): a dict with current attendance status, |
73 an empty dict is returned if nothing has been answered yed | 272 an empty dict is returned if nothing has been answered yed |
74 """ | 273 """ |
75 items, metadata = yield self._p.getItems(service, node, item_ids=[client.jid.userhost()], profile_key=client.profile) | 274 items, metadata = yield self._p.getItems(service, node, item_ids=[client.jid.userhost()], profile_key=client.profile) |
76 try: | 275 try: |
77 event_elt = next(items[0].elements((NS_EVENT, u'event'))) | 276 event_elt = next(items[0].elements(NS_EVENT, u'invitee')) |
78 except IndexError: | 277 except IndexError: |
79 # no item found, event data are not set yet | 278 # no item found, event data are not set yet |
80 defer.returnValue({}) | 279 defer.returnValue({}) |
81 data = {} | 280 data = {} |
82 for key in (u'attend', u'guests'): | 281 for key in (u'attend', u'guests'): |
84 data[key] = event_elt[key] | 283 data[key] = event_elt[key] |
85 except KeyError: | 284 except KeyError: |
86 continue | 285 continue |
87 defer.returnValue(data) | 286 defer.returnValue(data) |
88 | 287 |
89 def _eventSet(self, service, node, event_data, profile_key): | 288 def _eventInviteeSet(self, service, node, event_data, profile_key): |
90 service = jid.JID(service) if service else None | 289 service = jid.JID(service) if service else None |
91 node = node if node else NS_EVENT | 290 node = node if node else NS_EVENT |
92 client = self.host.getClient(profile_key) | 291 client = self.host.getClient(profile_key) |
93 return self.eventSet(client, service, node, event_data) | 292 return self.eventInviteeSet(client, service, node, event_data) |
94 | 293 |
95 def eventSet(self, client, service, node, data): | 294 def eventInviteeSet(self, client, service, node, data): |
96 """Set or update attendance data in event node | 295 """Set or update attendance data in event node |
97 | 296 |
98 @param service(unicode, None): PubSub service | 297 @param service(unicode, None): PubSub service |
99 @param node(unicode): PubSub node of the event | 298 @param node(unicode): PubSub node of the event |
100 @param data(dict[unicode, unicode]): data to update | 299 @param data(dict[unicode, unicode]): data to update |
101 key can be: | 300 key can be: |
102 attend: one of "yes", "no", "maybe" | 301 attend: one of "yes", "no", "maybe" |
103 guests: an int | 302 guests: an int |
104 """ | 303 """ |
105 event_elt = domish.Element((NS_EVENT, 'event')) | 304 event_elt = domish.Element((NS_EVENT, 'invitee')) |
106 for key in (u'attend', u'guests'): | 305 for key in (u'attend', u'guests'): |
107 try: | 306 try: |
108 event_elt[key] = data.pop(key) | 307 event_elt[key] = data.pop(key) |
109 except KeyError: | 308 except KeyError: |
110 pass | 309 pass |