The infrastructure for cardstories plugin was implemented. Each plugin may register when it loads and listen to game events and/or transform the incoming HTTP requests or the outgoing JSON result.
plugin infrastructure
A plugin is a single python file located at /usr/share/cardstories/plugin/plugin.py. It must provide a single class named Plugin that is instantiated with a single parameter : an instance of CardstoriesService.
class Plugin: def __init__(self, service): pass def name(self): return 'NAME'
The plugin is loaded if it shows in the –plugins option. For instance if cardstories is run with –plugins NAME the /usr/share/cardstories/plugins/NAME.py file will be loaded. The name method must return a string that is the name of the plugin that can be used in the –plugins-pre-process and –plugin-post-process options.
Each plugin is given a directory to store information in /var/lib/cardstories/plugins/NAME and a directory to store configuration files in /etc/cardstories/plugins/NAME. These paths are available in the services.settings map as follows:
class Plugin: def __init__(self, service): os.path.join(service.settings['plugins-libdir'], self.name()) # /var/lib/cardstories/plugins/NAME os.path.join(service.settings['plugins-confdir'], self.name()) # /etc/cardstories/plugins/NAME def name(self): return 'NAME'
listening to the service
A plugin can register to listen to all events occuring on the cardstories service as follows:
class Plugin: def __init__(self, service): self.service = service self.accept(None) def accept(self, event): self.service.plugin_event = event if event == None: pass # startService() was called on CardstoriesService elif event['type'] == 'start': pass # stopService() is about to be called on CardstoriesService elif event['type'] == 'stop': pass # a game is about to be deleted from memory elif event['type'] == 'delete': event['game'] # CardstoriesGame instance # a game was modified elif event['type'] == 'change': event['game'] # CardstoriesGame instance # a player is given cards to pick if event['details']['type'] == 'participate': event['details']['player_id'] # numerical player id # the author went to voting state elif event['details']['type'] == 'voting': pass # the player picks a card elif event['details']['type'] == 'pick': event['details']['player_id'] # numerical player id event['details']['card'] # numerical card id # the player votes for a card elif event['details']['type'] == 'vote': event['details']['player_id'] # numerical player id event['details']['vote'] # numerical card id # the author went to game complete state elif event['details']['type'] == 'complete': pass # the author invited players to play elif event['details']['type'] == 'invite': event['details']['invited'] # list of numerical player ids self.service.listen().addCallback(self.accept)
The accept function should act depending on the event, as shown above. When it is done it must register again to be notified of the next event.
HTTP request filtering
The plugins listed in the –plugins-pre-process option will be called, in that order, with the incoming HTTP request. For instance if –plugin-pre-process NAME is given to cardstories, the preprocess method of the Plugin instance will be called.
def preprocess(self, result, request): request.preprocess = 'preprocess' return result
The example preprocess function adds a new data member to the Request object. It must return the result argument untouched.
The plugins listed in the –plugins-post-process option will be called, in that order, with the resulting JSON request. For instance if –plugin-post-process NAME is given to cardstories, the postprocess method of the Plugin instance will be called.
def postprocess(self, result): result['postprocess'] = self.preprocess return result
The example postprocess function adds a entry to the map that will be converted into a JSON object before being sent back to the HTTP client. It must return a map with the entry error set in case an error must be notified to the client.
The plugin names listed in the –plugin-post-process and –plugin-pre-process options are those returned by the name functions of the corresponding Plugin instance. Although it is best to name the plugin file with the same name as the one returned by the name function to avoid confusion, it is not mandatory.
authentication
The default authentication module has been transformed into a plugin. The hack introduced to send invitation emails was removed.