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