cardstories event notifications and plugins

An event notification system was added to the CardstoriesService class so that third party code can listen to it. It will help implement email notifications. The architecture of a plugin system was designed.

Event notifications

email notifications to game events needs to know when they occur. But they also need to know about the player email. By design, the CardstoriesService and CardstoriesGame know nothing about the player emails. This is considered part of the user database and the role of these classes is solely to implement the game logic. Although it would be possible to add the player email to these structures, it would quickly get out of hands. The number of features and behaviors associated to user managements, specially in the context of a social network, it unbounded.
Instead of making CardstoriesService a derivative of pollable, two new functions were implemented : notify and listen.
listen() returns a deferred that will be called when a) the service is started (type start), b) the service is stopped (type stop), c) a game is deleted (type delete), d) a game has changed (type change). The result argument of the callback is a map with a “type” member set as explained above. When the type is either “delete” or “change”, it the “game” member is the CardstoriesGame object to which the even relates. The “detail” member further describes the event when the “type” is “change” or it is set to None when the “type” is “delete”.

def notify(self, result):
        if hasattr(self, 'notify_running'):
            raise UserWarning, 'recursive call to notify'
        self.notify_running = True
        listeners = self.listeners
        self.listeners = []
        def error(reason):
            reason.printDetailedTraceback()
            return True
        for listener in listeners:
            listener.addErrback(error)
            listener.callback(result)
        del self.notify_running

This would not be enough for a listener to figure out what event triggered the notification. The CardstoriesGame class was modified to include enough information in the notification argument so that the listener knows what happened.

self.touch(type = 'participate', player_id = player_id)
self.touch(type = 'voting')
self.touch(type = 'pick', player_id = player_id, card = card)
self.touch(type = 'vote', player_id = player_id, vote = vote)
self.touch(type = 'complete')
self.touch(type = 'invite', invited = invited)

plugin system design

A cardstories plugin (foobar for instance) would be a python file located in –plugins-dir (/usr/share/cardstories/plugins by default, hence /usr/share/cardstories/plugins/foobar.py) and its configuration files could be located in –plugins-confdir (/etc/cardstories/plugins by default, hence /etc/cardstories/plugins/foobar) and it could store information in –plugins-libdir (/var/lib/cardstories/plugins by default, hence /var/lib/cardstories/plugin/foobar).
The list of plugins to be activated would be given to the –plugins argument as a white space separated list of plugin names. Each of them would be required to provide the init function which would be called with a CardstoriesService object, before it is started. They would be expected to call listen as follows:

def action(event, service):
  if event['type'] == 'start':
     pass
  elif event['type'] == 'stop':
     pass
  elif event['type'] == 'change':
     if event['details']['type'] == 'voting':
        pass
     elif event['details']['type'] == 'invite':
        pass
  service.listen().addCallback(lambda event: action(event, service))
  return event

def init(service):
 action({'type':None}, service)

The plugin could also be used to pre-process each incoming query. The Request object representing the incoming HTTP request would be given to each plugin listed in the –plugin-pre-process argument. The plugin could examine the content of the request and modify its content. For instance, it could replace the player names from emails into unique integers.
The plugin could also be used to post-process each answer. The JSON object representing the return value of the answer to the HTTP request would be given to each plugin listed in the –plugin-post-process argument. The plugin could examine the content of the JSON object and modify its content. For instance, it could replace the numerical id of each player into human readable emails.
For init, pre-process and post-process lists, the plugins would be invoked in the order in which they are listed.