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
This commit is contained in:
Goffi 2017-05-19 12:43:41 +02:00
commit fb1e9bf53a
2 changed files with 212 additions and 13 deletions

View file

@ -18,11 +18,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from sat.core import exceptions
from sat.core.constants import Const as C
from sat.core.log import getLogger
log = getLogger(__name__)
from sat.tools import utils
from sat.tools.common import uri as uri_parse
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber import jid, error
from twisted.words.xish import domish
from wokkel import pubsub
@ -49,22 +52,218 @@ class Events(object):
self.host = host
self._p = self.host.plugins["XEP-0060"]
host.bridge.addMethod("eventGet", ".plugin",
in_sign='sss', out_sign='a{ss}',
in_sign='ssss', out_sign='(ia{ss})',
method=self._eventGet,
async=True)
host.bridge.addMethod("eventSet", ".plugin",
host.bridge.addMethod("eventCreate", ".plugin",
in_sign='ia{ss}ssss', out_sign='s',
method=self._eventCreate,
async=True)
host.bridge.addMethod("eventModify", ".plugin",
in_sign='sssia{ss}s', out_sign='',
method=self._eventModify,
async=True)
host.bridge.addMethod("eventInviteeGet", ".plugin",
in_sign='sss', out_sign='a{ss}',
method=self._eventInviteeGet,
async=True)
host.bridge.addMethod("eventInviteeSet", ".plugin",
in_sign='ssa{ss}s', out_sign='',
method=self._eventSet,
method=self._eventInviteeSet,
async=True)
def _eventGet(self, service, node, profile_key):
def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventGet(client, service, node)
return self.eventGet(client, service, node, id_)
@defer.inlineCallbacks
def eventGet(self, client, service, node):
def eventGet(self, client, service, node, id_=NS_EVENT):
"""Retrieve event data
@param service(unicode, None): PubSub service
@param node(unicode): PubSub node of the event
@param id_(unicode): id_ with even data
@return (tuple[int, dict[unicode, unicode]): event data:
- timestamp of the event
- event metadata where key can be:
location: location of the event
image: URL of a picture to use to represent event
background-image: URL of a picture to use in background
an empty dict is returned if nothing has been answered yed
"""
if not id_:
id_ = NS_EVENT
items, metadata = yield self._p.getItems(service, node, item_ids=[id_], profile_key=client.profile)
try:
event_elt = next(items[0].elements(NS_EVENT, u'event'))
except IndexError:
raise exceptions.NotFound(_(u"No event with this id has been found"))
try:
timestamp = utils.date_parse(next(event_elt.elements(NS_EVENT, "date")))
except StopIteration:
timestamp = -1
data = {}
for key in (u'name',):
try:
data[key] = event_elt[key]
except KeyError:
continue
for elt_name in (u'description',):
try:
elt = next(event_elt.elements(NS_EVENT, elt_name))
except StopIteration:
continue
else:
data[elt_name] = unicode(elt)
for elt_name in (u'image', 'background-image'):
try:
image_elt = next(event_elt.elements(NS_EVENT, elt_name))
data[elt_name] = image_elt['src']
except StopIteration:
continue
except KeyError:
log.warning(_(u'no src found for image'))
for uri_type in (u'invitees', u'blog'):
try:
elt = next(event_elt.elements(NS_EVENT, 'invitees'))
uri = data[uri_type + u'_uri'] = elt['uri']
uri_data = uri_parse.parseXMPPUri(uri)
if uri_data[u'type'] != u'pubsub':
raise ValueError
except StopIteration:
log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type))
except KeyError:
log.warning(_(u"incomplete {uri_type} element").format(uri_type=uri_type))
except ValueError:
log.warning(_(u"bad {uri_type} element").format(uri_type=uri_type))
else:
data[uri_type + u'_service'] = uri_data[u'path']
data[uri_type + u'_node'] = uri_data[u'node']
for meta_elt in event_elt.elements(NS_EVENT, 'meta'):
key = meta_elt[u'name']
if key in data:
log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml()))
continue
data[key] = unicode(meta_elt)
defer.returnValue((timestamp, data))
def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
@defer.inlineCallbacks
def eventCreate(self, client, timestamp, data, service, node=None, item_id=NS_EVENT):
"""Create or replace an event
@param service(jid.JID, None): PubSub service
@param node(unicode, None): PubSub node of the event
None will create instant node.
@param item_id(unicode): ID of the item to create.
@param timestamp(timestamp, None)
@param data(dict[unicode, unicode]): data to update
dict will be cleared, do a copy if data are still needed
key can be:
- name: name of the event
- description: details
- image: main picture of the event
- background-image: image to use as background
@return (unicode): created node
"""
if not item_id:
raise ValueError(_(u"item_id must be set"))
if not service:
service = client.jid.userhostJID()
event_elt = domish.Element((NS_EVENT, 'event'))
if timestamp is not None and timestamp != -1:
formatted_date = utils.xmpp_date(timestamp)
event_elt.addElement((NS_EVENT, 'date'), content=formatted_date)
for key in (u'name',):
if key in data:
event_elt[key] = data.pop(key)
for key in (u'description',):
if key in data:
event_elt.addElement((NS_EVENT, key), content=data.pop(key))
for key in (u'image', u'background-image'):
if key in data:
elt = event_elt.addElement((NS_EVENT, key))
elt['src'] = data.pop(key)
# we first create the invitees and blog nodes (if not specified in data)
for uri_type in (u'invitees', u'blog'):
key = uri_type + u'_uri'
if key not in data:
# FIXME: affiliate invitees
uri_node = yield self._p.createNode(client, service)
yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST})
uri_service = service
else:
# we suppose that *_service and *_node are present
# FIXME: handle cases when they are not
uri_service = data.pop(uri_type + u'_service')
uri_node = data.pop(uri_type + u'_node')
del data[key]
elt = event_elt.addElement((NS_EVENT, uri_type))
elt['uri'] = uri_parse.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node)
# remaining data are put in <meta> elements
for key in data.keys():
elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key))
elt['name'] = key
item_elt = pubsub.Item(id=item_id, payload=event_elt)
try:
# TODO: check auto-create, no need to create node first if available
node = yield self._p.createNode(client, service, nodeIdentifier=node)
except error.StanzaError as e:
if e.condition == u'conflict':
log.debug(_(u"requested node already exists"))
yield self._p.publish(service, node, items=[item_elt], profile_key=client.profile)
defer.returnValue(node)
def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventModify(client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update)
@defer.inlineCallbacks
def eventModify(self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None):
"""Update an event
Similar as create instead that it update existing item instead of
creating or replacing it. Params are the same as for [eventCreate].
"""
event_timestamp, event_metadata = yield self.eventGet(client, service, node, id_)
new_timestamp = event_timestamp if timestamp_update is None else timestamp_update
new_data = event_metadata
if data_update:
for k, v in data_update.iteritems():
new_data[k] = v
yield self.eventCreate(client, new_timestamp, new_data, service, node, id_)
def _eventInviteeGet(self, service, node, profile_key):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventInviteeGet(client, service, node)
@defer.inlineCallbacks
def eventInviteeGet(self, client, service, node):
"""Retrieve attendance from event node
@param service(unicode, None): PubSub service
@ -74,7 +273,7 @@ class Events(object):
"""
items, metadata = yield self._p.getItems(service, node, item_ids=[client.jid.userhost()], profile_key=client.profile)
try:
event_elt = next(items[0].elements((NS_EVENT, u'event')))
event_elt = next(items[0].elements(NS_EVENT, u'invitee'))
except IndexError:
# no item found, event data are not set yet
defer.returnValue({})
@ -86,13 +285,13 @@ class Events(object):
continue
defer.returnValue(data)
def _eventSet(self, service, node, event_data, profile_key):
def _eventInviteeSet(self, service, node, event_data, profile_key):
service = jid.JID(service) if service else None
node = node if node else NS_EVENT
client = self.host.getClient(profile_key)
return self.eventSet(client, service, node, event_data)
return self.eventInviteeSet(client, service, node, event_data)
def eventSet(self, client, service, node, data):
def eventInviteeSet(self, client, service, node, data):
"""Set or update attendance data in event node
@param service(unicode, None): PubSub service
@ -102,7 +301,7 @@ class Events(object):
attend: one of "yes", "no", "maybe"
guests: an int
"""
event_elt = domish.Element((NS_EVENT, 'event'))
event_elt = domish.Element((NS_EVENT, 'invitee'))
for key in (u'attend', u'guests'):
try:
event_elt[key] = data.pop(key)

View file

@ -31,7 +31,7 @@ from sat.tools import sat_defer
from sat.tools import utils
from sat.tools.common import data_format
# XXX: tmp.pubsub is actually use instead of wokkel version
# XXX: tmp.pubsub is actually used instead of wokkel version
from wokkel import pubsub
import uuid
import time