====================================
How to create plugins and components
====================================

This document is meant for developers who are interested in developing their 
first Elisa plugin. It will guide you through all the steps without requiring
any previous knowledge apart from Python programming.


.. sectnum::

.. contents::


What is a plugin? What are components?
======================================

A component in Elisa is a Python class that inherits from the Component class.
It allows it to be distributed apart from the core of Elisa and still be usable
by others. To ship a component, it has to be put in a plugin. Plugins are
containers for components.


Creating a plugin
=================

A plugin is a directory containing:

- a configuration file called 'plugin.conf'
- a __init__.py file that makes it a Python package
- Python source files containing components

The plugin configuration file is divided in sections which contains their own
properties. One is mandatory, the "general" section:

::

 [general]
 name="Name_of_your_plugin"
 version="0.1"
 license="GPL"
 author="John Doe"
 author_email="john@doe.com"
 keywords=['audio','whatever']
 summary="short description of the plugin"
 description="Description of the plugin"
 plugin_dependencies=[]
 external_dependencies=[]


'plugin_dependencies' contains a list of inter-plugins dependencies. For example,
if one of your components requires another plugin to be present, you should
add the name of this plugin there.

'external_dependencies' should be filled with python packages you are
using in your components. It should be filled with the exact names of the modules
your are importing.

The other properties will be used by the plugin repository which will
store them as plugin's metadata, if you some day want to ship your
plugin in a repository, for the user community.

By default, Elisa will look for plugins in the directory defined in the
ELISA_PLUGIN_PATH environment variable.


Adding a component to a plugin
==============================

The optional sections in a plugin configuration file are for declaring the
components attached to your plugins.
The section name should contain the python path to your component followed by
a colon (":") and the name of your component class: we call that the
'component path'.

For example if we have in the plugin directory the file
my_components/test_component.py that contains your class "TestComponent", you
should name your section as follows: 


::

 [my_components.test_component:TestComponent]


This section can contain several properties:


::

 name:                      a string representing the internal name of the
                            component
 description:               description string
 platforms:                 if your plugin uses platform-dependent code, you
                            should add the platform name to that list
 external_dependencies:     a list of external dependencies
 component_dependencies:    list of components your component depends on


Example: 


::

 [metadata_providers.taglib_metadata:TaglibMetadata]
 description="Local audio files generaldata retrieval support"
 platforms=["linux",]
 external_dependencies=["tagpy",]
 component_dependencies=[]


These properties are *optional*. The Component class name (in this
example, TaglibMetadata) will become the component's name with upcase
characters lowered and underscores added like in the following
example:

::

 TaglibMetadata -> taglib_metadata

If you want to override this behavior, just add a "name" option in the
component section like this:

::

 [metadata_providers.taglib_metadata:TaglibMetadata]
 name = "tagpy"
 ...



Developing your component
=========================

You first need to know which type of component you will be writing depending
on what you want to achieve.
There are currently more than 10 different component types in Elisa. Each of
them has an API defined in their corresponding Python module in
elisa/base_components/. Each time you make a new component you have to inherit
from one of the components base classes.

Here is a list of each component type and their description:

- Input Provider:

    Provide a way for users to interact with Elisa.

    Examples: keyboard, mouse, remote control, network control, etc.

- Media Provider:

    Allows accessing and interacting with media files and repositories.

    Examples: HTTP, Samba, local files, etc.

- Metadata Provider

    Allows metadata to be extracted from media or fetched from webservices.

    Examples: EXIF data, ID3 tags, IMDB infos, etc.

- Service Provider:

    Runs during all the lifetime of an Elisa instance and provides services to
    other components.

    Examples: HAL listening daemon, HTTP server, etc.

- Model:

    It is a data structure filled when information has to be displayed to the
    user.

    Examples: menu, list, movie infos, etc.

- View:

    Responsible for rendering information contained in a model. It can render
    it using graphics, sound or even network output.

    Examples: music player, slideshow, 3D menu, etc.

- Controller:

    Responsible for translating user inputs coming from input providers into
    modification on models and controllers. Also holds rendering data that is
    shareable between different views.

- Theme:

    Provides a selection of icons, fonts and colours to Views.

- Activity:

    Creates models and fills them with data fetched from wherever it fits.

    Examples: weather forecast, etc.

- Action:

    Contains modifications applied to models and controllers when it gets
    activated.

    Examples: play media, enqueue media, preview media, etc.

- Playlist:

    Encapsulates a media playlist generation algorithm.

    Examples: dynamic lastfm based, etc.
    

A detailed description of their inner workings can be found in the API
documentation of each of the base components. Also, a set of tutorials
covering each of them is available in the other howtos shipped with the one
you are currently reading.


Modifying your Elisa configuration to load your plugin and component
====================================================================

Now that you have created your plugin and added your component to it, you need
to modify your Elisa global configuration to load them.

The place where you have to add a reference of your component depends of the
type of component you have created.

For example, if you have created a media provider 'my_media_provider' in a
plugin 'my_plugin', you will need to add your component path in the property
'media_providers' in the [general] section of the Elisa config file:

::
 
 media_providers = ['base:upnp_media', 'base:ipod_media', 'base:local_media', 'my_plugin:my_media_provider']


Be careful, the config doesn't support multi-lines options. Once this
is setup, the last step is to launch Elisa telling it where to find
your plugin:

::

 ELISA_PLUGIN_PATH=myplugin_path/ python elisa.py

If the plugin (or component) fails to load, debug the plugin_registry,
the output will be quite verbose but if you read it carefully you
might find out why the registry failed to load your plugin:

::

 ELISA_DEBUG=plugin_registry:4 ELISA_PLUGIN_PATH=myplugin_path/ python elisa.py

Using a configuration file for your component
=============================================

Your components can have their own section in the Elisa configuration file.

You can access the config object (declared in elisa.extern.configobj.ConfigObj)
through your component's 'config' property (self.config).

You can also declare a default configuration in a class variable called
'default_config', as well as the config documentation in 'config_doc':

.. code-block:: python

    class AudioActivity(MediaMenuActivity):

        config_doc = {'locations': 'the audio media locations',
                      'cover_files': 'filenames to display as album cover '\
                                     'if available in an album directory'
                      }

        default_config = {'locations': ['file://./sample_data/music/',],
                          'cover_files': ['front.jpg', 'cover.jpg']
                          }

Accessing the configuration properties is easy, you just need to call the 
config.get(property_name, default_value):

.. code-block:: python

    cover_files = self.config.get('cover_files', ['front.jpg', 'cover.jpg'])

Be careful, self.config is not available in the __init__ of a component. If
you need it during its initialization, use the initialize method.

