[grilo/0.1.x] doc: Added a chapter on plugin development.



commit e0ea1c12b49a4a35125b0c6a705b2c6f4970b65e
Author: Iago Toral Quiroga <itoral igalia com>
Date:   Thu Jun 16 13:35:58 2011 +0200

    doc: Added a chapter on plugin development.

 doc/grilo/Makefile.am                              |    5 +-
 doc/grilo/grilo-docs.sgml                          |   19 +
 doc/grilo/quick-start-plugins-media-sources.xml    | 1157 ++++++++++++++++++++
 doc/grilo/quick-start-plugins-metadata-sources.xml |  392 +++++++
 doc/grilo/quick-start-plugins-testing.xml          |   99 ++
 5 files changed, 1671 insertions(+), 1 deletions(-)
---
diff --git a/doc/grilo/Makefile.am b/doc/grilo/Makefile.am
index d002cb2..d533007 100644
--- a/doc/grilo/Makefile.am
+++ b/doc/grilo/Makefile.am
@@ -73,7 +73,10 @@ HTML_IMAGES=
 # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
 # e.g. content_files=running.sgml building.sgml changes-2.0.sgml
 content_files=overview.xml	\
-	quick-start-using-grilo.xml
+	quick-start-using-grilo.xml \
+	quick-start-plugins-media-sources.xml \
+	quick-start-plugins-metadata-sources.xml \
+	quick-start-plugins-testing.xml
 
 # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
 # These files must be listed here *and* in content_files
diff --git a/doc/grilo/grilo-docs.sgml b/doc/grilo/grilo-docs.sgml
index 6a499a9..60c9f59 100644
--- a/doc/grilo/grilo-docs.sgml
+++ b/doc/grilo/grilo-docs.sgml
@@ -22,10 +22,29 @@
 
   <reference>
     <title>Quick Start</title>
+
     <chapter>
     <title>Using Grilo</title>
     <xi:include href="quick-start-using-grilo.xml"/>
     </chapter>
+
+    <chapter>
+      <title>Writing plugins for Grilo</title>
+      <section id="quick-start-writing-plugins">
+        <section id="media-source-plugins">
+          <title>Media Source plugins</title>
+          <xi:include href="quick-start-plugins-media-sources.xml"/>
+        </section>
+        <section id="metadata-source-plugins">
+          <title>Metadata Source plugins</title>
+          <xi:include href="quick-start-plugins-metadata-sources.xml"/>
+        </section>
+        <section id="testing-plugins">
+          <title>Testing your plugins</title>
+          <xi:include href="quick-start-plugins-testing.xml"/>
+        </section>
+      </section>
+    </chapter>
   </reference>
 
   <reference>
diff --git a/doc/grilo/quick-start-plugins-media-sources.xml b/doc/grilo/quick-start-plugins-media-sources.xml
new file mode 100644
index 0000000..63afe61
--- /dev/null
+++ b/doc/grilo/quick-start-plugins-media-sources.xml
@@ -0,0 +1,1157 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"; [
+<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+
+<section>
+<section id="media-source-plugins-intro">
+  <title>Introduction</title>
+
+  <para>
+    Media Source plugins provide access to media content. Examples
+    of Media Source plugins are the Jamendo or UPnP plugins, which
+    give access to content offered by Jamendo or content available
+    on UPnP servers respectively.
+  </para>
+
+  <para>
+    Usually, clients interact with these plugins in various ways:
+    <itemizedlist>
+      <listitem>
+        <emphasis>Search.</emphasis>
+        Users can instruct the media provider to search for
+        content that matches certain keywords. This is how people
+        typically interact with services like YouTube, for example.
+      </listitem>
+      <listitem>
+        <emphasis>Browse.</emphasis>
+        Users navigate through a fixed hierarchy of
+        categorized content interactively. This is how people
+        typically interact with UPnP services, for example.
+      </listitem>
+      <listitem>
+        <emphasis>Query.</emphasis>
+        Some times services provide features that
+        are too specific to be transported to a generic,
+        cross-service API. An example of this could be certain
+        search filtering options. Queries allow users to 
+        interact with services using service-specific language
+        that can be used to exploit these features.
+      </listitem>
+      <listitem>
+        <emphasis>Metadata.</emphasis>
+        Users can request additional information (metadata)
+        for a specific media item served by a media provider through
+        a previous browse, search or query operation that was configured
+        to retrieve only partial metadata (typically for optimization
+        purposes). Metadata operations are usually used when showing
+        detailed information about specific media items.
+      </listitem>
+      <listitem>
+        <emphasis>Store.</emphasis>
+        Some media providers allow (or even require) users
+        to push content to them. This is how people interact with
+        Podcasts for example, they "store" the feeds they are
+        interested in following first.
+      </listitem>
+      <listitem>
+        <emphasis>Remove.</emphasis>
+        The opposite to the Store operation, used to remove
+        content from the source.
+      </listitem>
+    </itemizedlist> 
+  </para>
+</section>
+
+<section id="media-source-plugins-basics">
+  <title>Registering Media Source Plugins</title>
+  
+  <para>
+    Grilo plugins must use the macro GRL_PLUGIN_REGISTER, which
+    defines the entry and exit points of the plugin (called
+    when the plugin is loaded and unloaded respectively) as well
+    as its plugin identifier (a string identifying the plugin).
+  </para>
+
+  <para>
+    The plugin identifier will be used by clients to identify
+    the plugin when interacting with the plugin registry API. See
+    the <link linkend="GrlPluginRegistry">GrlPluginRegistry</link>
+    API reference for more details.
+  </para>
+
+  <para>
+    The plugin initialization function is mandatory.
+    the plugin deinitialization function is optional.
+  </para>
+
+  <para>
+    Usually the plugin initialization function will create
+    at least one GrlMediaSource instance and register it
+    using grl_plugin_registry_register_source.
+  </para>
+
+  <para>
+    A GrlMediaSource instance represents a particular source
+    of media. Usually each plugin would spawn just one media
+    source, but some plugins may spawn multiple media sources.
+    For example, a UPnP plugin spawning one media source object
+    for each UPnP server discovered.
+  </para>
+
+  <para>
+    Users can query the registry for available media sources and
+    then use the GrlMediaSource API to interact with them.
+  </para>
+
+  <para>
+    If the plugin requires configuration this should be processed
+    during the plugin initialization function, which should return
+    TRUE upon successful initialization or FALSE otherwise.
+  </para>
+
+  <para>
+    The parameter "configs" of the plugin initialization function
+    provides available configuration information provided by the
+    user for this plugin, if any. This parameter is a list of
+    GrlConfig objects. Usually there would be only one GrlConfig
+    object in the list, but there might be more in the cases of
+    plugins spawning multiple media sources that require different
+    configuration options.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+gboolean
+grl_foo_plugin_init (GrlPluginRegistry *registry,
+                     const GrlPluginInfo *plugin,
+                     GList *configs)
+{
+  gchar *api_key;
+  GrlConfig *config;
+
+  config = GRL_CONFIG (configs->data);
+
+  api_key = grl_config_get_api_key (config);
+  if (!api_key) {
+    GRL_INFO ("Missing API Key, cannot load plugin");
+    return FALSE;
+  }
+
+  GrlFooSource *source = grl_foo_source_new (api_key);
+  grl_plugin_registry_register_source (registry,
+                                       plugin,
+                                       GRL_MEDIA_PLUGIN (source),
+                                       NULL);
+  g_free (api_key);
+  return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_foo_plugin_init, NULL, "grl-foo");
+]]>
+  </programlisting>
+
+  <para>
+    The next step is to implement the plugin code, for that
+    Media Source plugins must extend the
+    <link linkend="GrlMediaSource">GrlMediaSource</link> class.
+  </para>
+
+  <para>
+    In typical GObject fashion, developers should use the G_DEFINE_TYPE macro,
+    and then provide the class initialization function
+    (grl_foo_source_class_init in the example below) and the instance
+    initialization function (grl_foo_source_init in the example below).
+    A constructor function, although not mandatory, is usually nice to
+    have (grl_foo_source_new in the example below).
+  </para>
+
+  <para>
+    When creating a new GrlMediaSource instance, a few properties
+    should be provided:
+    <itemizedlist>
+      <listitem>
+        <emphasis>source-id:</emphasis> An identifier for the source object.
+        This is not the same as the plugin identifier (remember that a plugin
+        can spawn multiple media source objects). This identifier can be
+        used by clients when interacting with available media sources
+        through the plugin registry API. See
+        the <link linkend="GrlPluginRegistry">GrlPluginRegistry</link>
+        API reference for more details.
+      </listitem>
+      <listitem>
+        <emphasis>source-name:</emphasis> A name for the source object
+        (typically the name that clients would show in the user interface).
+      </listitem>
+      <listitem>
+        <emphasis>source-desc</emphasis>: A description of the media source.
+      </listitem>
+    </itemizedlist>
+  </para>
+
+  <para>
+    In the class initialization function the plugin developer should
+    provide implementations for the operations that the plugin will
+    support. Most operations are optional, but for media sources
+    at least one of Search, Browse and Query are expected to be
+    implemented. Store and Remove are optional. Metadata is expected
+    to be implemented, just like supported_keys. Slow_keys is optional.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+/* Foo class initialization code */
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+  metadata_class->supported_keys = grl_foo_source_supported_keys;
+  metadata_class->slow_keys = grl_foo_source_supported_keys;
+
+  source_class->browse = grl_foo_source_browse;
+  source_class->search = grl_foo_source_search;
+  source_class->query = grl_foo_source_query;
+  source_class->store = grl_foo_source_store;
+  source_class->remove = grl_foo_source_remove;
+  source_class->metadata = grl_foo_source_metadata;
+}
+
+/* Foo instance initialization code */
+static void
+grl_foo_source_init (GrlFooSource *source)
+{
+  /* Here you would initialize 'source', which is an instance
+     of this class type. */
+  source->api_key = NULL;
+}
+
+/* GrlFooSource constructor */
+static GrlFooSource *
+grl_foo_source_new (const gchar *api_key)
+{
+  GrlFooSource *source;
+
+  source = GRL_FOO_SOURCE (g_object_new (GRL_FOO_SOURCE_TYPE,
+                                         "source-id", "grl-foo",
+                                         "source-name", "Foo",
+                                         "source-desc", "Foo media provider",
+                                         NULL));
+  source->api_key = g_strdup (api_key);
+  return source;
+}
+
+G_DEFINE_TYPE (GrlFooSource, grl_foo_source, GRL_TYPE_MEDIA_SOURCE);
+]]>
+  </programlisting>
+</section>
+
+<section id="media-source-plugins-supported-keys">
+  <title>Implementing Supported Keys</title>
+  
+  <para>
+    An implementation for the "supported_keys" method is mandatory for
+    the plugin to work.
+  </para>
+
+  <para>
+    This method is declarative, and it only has to return a list of 
+    metadata keys that the plugin supports, that is, it is a declaration
+    of the metadata that the plugin can provide for the media content
+    that it exposes.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+  metadata_class->supported_keys = grl_foo_source_supported_keys;
+}
+
+static const GList *
+grl_foo_source_supported_keys (GrlMetadataSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+                                      GRL_METADATA_KEY_TITLE,
+                                      GRL_METADATA_KEY_URL,
+                                      GRL_METADATA_KEY_THUMBNAIL,
+                                      GRL_METADATA_KEY_MIME,
+                                      GRL_METADATA_KEY_ARTIST,
+                                      GRL_METADATA_KEY_DURATION,
+                                      NULL);
+  }
+  return keys;
+}
+]]>
+  </programlisting>
+</section>
+
+<section id="media-source-plugins-slow-keys">
+  <title>Implementing Slow Keys</title>
+  
+  <para>
+    Implementation of the "slow_keys" method is optional, but in some
+    cases it can help to improve performance remarkably.
+  </para>
+
+  <para>
+    This method is intended to provide the framework with information
+    on metadata that is particularly expensive for the framework
+    to retrieve. The framework (or the plugin users) can then 
+    use this information to remove this metadata from their requests
+    when performance is important. This is, again, a declarative
+    interface providing a list of keys.
+  </para>
+
+  <para>
+    If the plugin does not provide an implementation for "slow_keys"
+    the framework assumes that all keys are equally expensive to
+    retrieve and will not perform optimizations in any case.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+  metadata_class->slow_keys = grl_foo_source_slow_keys;
+}
+
+static const GList *
+grl_foo_source_slow_keys (GrlMetadataSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL,
+                                      NULL);
+  }
+  return keys;
+}
+]]>
+  </programlisting>
+</section>
+
+<section id="media-source-plugins-search">
+  <title>Implementing Search</title>
+  
+  <para>
+    Implementation of the "search" method is optional, but at least one
+    of Search, Browse and Query are expected to be implemented.
+  </para>
+
+  <para>
+    This method implements text based searches, retrieving media
+    that matches the text keywords provided by the user.
+  </para>
+
+  <para>
+    Typically, the way this method operates is like this:
+    <itemizedlist>
+      <listitem>Plugin checks the input parameters and encodes the
+        search operation as expected by the service provider
+        (that could be a SQL query, a HTTP request, etc)</listitem>
+      <listitem>Plugin executes the search on the backend. Typically this
+        involves some kind blocking operation (networking, disk access, etc)
+        that should be executed asynchronously when possible.</listitem>
+      <listitem>Plugin receives the results from the media provider.
+        For each result received the plugin creates a
+        GrlMedia object encapsulating the metadata obtained for
+        that particular match.</listitem>
+      <listitem>Plugin sends the GrlMedia objects back to the client
+        one by one by invoking the user provided callback.</listitem>
+    </itemizedlist>
+  </para>
+
+  <para>
+    Below you can see some source code that illustrates this process:
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+/* In this example we assume a media provider that can be
+   queried over http, and that provides its results in xml format */
+
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->search = grl_foo_source_search;
+}
+
+static void
+foo_execute_search_async_cb (gchar *xml, GrlMediaSourceSearchSpec *ss)
+{
+  GrlMedia *media;
+  gint count;
+
+  count = count_results (xml);
+
+  if (count == 0) {
+    /* Signal "no results" */
+    ss->callback (ss->source, ss->operation_id,
+                  NULL, 0, ss->user_data, NULL);
+  } else {
+    /* parse_next parses the next media item in the XML
+       and creates a GrlMedia instance with the data extracted */
+    while (media = parse_next (xml))
+      ss->callback (ss->source,       /* Source emitting the data */
+                    ss->operation_id, /* Operation identifier */
+                    media,            /* Media that matched the query */
+                    --count,          /* Remaining count */
+                    ss->user_data,    /* User data for the callback */
+                    NULL);            /* GError instance (if an error occurred) */
+  }
+}
+
+static void
+grl_foo_source_search (GrlMediaSource *source, GrlMediaSourceSearchSpec *ss)
+{
+  gchar *foo_http_search:
+  
+  foo_http_search =
+    g_strdup_printf("http://media.foo.com?text=%s&offset=%d&count=%d";,
+                    ss->text, ss->skip, ss->count);
+
+  /* This executes an async http query and then invokes
+     foo_execute_search_async_cb with the response */
+  foo_execute_search_async (foo_http_search, ss);
+}
+]]>
+  </programlisting>
+
+  <para>
+    Please, check <link linkend="media-source-plugins-common-considerations">
+      Common considerations for Search, Browse and Query implementations</link>
+    for more information on how to implement Search operations properly.
+  </para>
+
+  <para>
+    Examples of plugins implementing Search functionality are
+    grl-jamendo, grl-youtube or grl-vimeo among others.
+  </para>
+</section>
+
+<section id="media-source-plugins-browse">
+  <title>Implementing Browse</title>
+  
+  <para>
+    Implementation of the "browse" method is optional, but at least one
+    of Search, Browse and Query are expected to be implemented.
+  </para>
+
+  <para>
+    Browsing is an interactive process, where users navigate by exploring
+    these boxes exposed by the media source in hierarchical form. The idea
+    of browsing a media source is the same as browsing a file system.
+  </para>
+
+  <para>
+    The signature and way of operation of the Browse operation is the same
+    as in the Search operation with one difference: instead of a text
+    parameter with the search keywords, it receives a GrlMedia object
+    representing the container (box) the user wants to browse.
+  </para>
+
+  <para>
+    For the most part, plugin developers that write Browse implementations
+    should consider the same rules and guidelines explained for
+    Search operations.
+  </para>
+
+  <para>
+    Below you can see some source code that illustrates this process:
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+/* In this example we assume a media provider that can be queried over
+   http, providing results in XML format. The media provider organizes
+   content according to a list of categories. */
+
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->browse = grl_foo_source_browse;
+}
+
+static void
+foo_execute_categories_async_cb (gchar *xml, GrlMediaSourceBrowseSpec *bs)
+{
+  GrlMedia *media;
+  gint count;
+
+  count = count_results (xml);
+
+  if (count == 0) {
+    /* Signal "no results" */
+    bs->callback (bs->source, bs->operation_id,
+                  NULL, 0, bs->user_data, NULL);
+  } else {
+    /* parse_next parses the next category item in the XML
+       and creates a GrlMedia instance with the data extracted,
+       which should be of type GrlMediaBox */
+    while (media = parse_next_cat (xml))
+      bs->callback (bs->source,       /* Source emitting the data */
+                    bs->operation_id, /* Operation identifier */
+                    media,            /* The category (box)  */
+                    --count,          /* Remaining count */
+                    bs->user_data,    /* User data for the callback */
+                    NULL);            /* GError instance (if an error occurred) */
+  }
+}
+
+static void
+foo_execute_media_async_cb (gchar *xml, GrlMediaSourceBrowseSpec *os)
+{
+  GrlMedia *media;
+  gint count;
+
+  count = count_results (xml);
+
+  if (count == 0) {
+    /* Signal "no results" */
+    bs->callback (bs->source, bs->operation_id,
+                  NULL, 0, bs->user_data, NULL);
+  } else {
+    /* parse_next parses the next media item in the XML
+       and creates a GrlMedia instance with the data extracted,
+       which should be of type GrlMediaImage, GrlMediaAudio or
+       GrlMediaVideo */
+    while (media = parse_next_media (xml))
+      os->callback (os->source,       /* Source emitting the data */
+                    os->operation_id, /* Operation identifier */
+                    media,            /* Media that matched the query */
+                    --count,          /* Remaining count */
+                    os->user_data,    /* User data for the callback */
+                    NULL);            /* GError instance (if an error occurred) */
+  }
+}
+
+static void
+grl_foo_source_browse (GrlMediaSource *source, GrlMediaSourceBrowseSpec *bs)
+{
+  gchar *foo_http_browse:
+  
+  /* We use the names of the categories as their media identifiers */
+  box_id = grl_media_get_id (bs->container);
+
+  if (!box_id) {
+    /* Browsing the root box, the result must be the list of
+       categories provided by the service */
+    foo_http_browse =
+      g_strdup_printf("http://media.foo.com/category_list";,
+                      os->skip, os->count);
+     /* This executes an async http query and then invokes
+        foo_execute_categories_async_cb with the response */
+     foo_execute_categories_async (foo_http_browse, os);
+  } else {
+    /* Browsing a specific category */
+    foo_http_browse =
+      g_strdup_printf("http://media.foo.com/content/%s?offset=%d&count=%d";,
+                      box_id, os->skip, os->count);
+     /* This executes an async http query and then invokes
+        foo_execute_browse_async_cb with the response */
+     foo_execute_media_async (foo_http_browse, os);
+  }
+}
+]]>
+  </programlisting>
+
+  <para>
+    Some considerations that plugin developers should take into account:
+    <itemizedlist>
+      <listitem>
+        In the example we are assuming that the content hierarchy only has two
+        levels, the first level exposes a list of categories (each one exposed
+        as a GrlMediaBox object so the user knows they can be browsed again), and
+        then a second level with the contents within these categories, that we
+        assume are all media items, although in real life they could very well
+        be more GrlMediaBox objects, leading to more complex hierarchies.
+      </listitem>
+      <listitem>
+        GrlMediaBox objects returned by a browse operation can be browsed
+        by clients in future Browse operations.
+      </listitem>
+      <listitem>
+        The input parameter that informs the plugin about the box that
+        should be browsed (bs->container) is of type GrlMediaBox. The
+        plugin developer must map that to something the media provider
+        understands. Typically, when GrlMedia objects are returned from
+        a plugin to the client, they are created so their "id"
+        property (grl_media_set_id) can be used for this purpose,
+        identifying these media resources uniquely in the context of
+        the media provider.
+      </listitem>
+      <listitem>
+        A GrlMediaBox object with NULL id always represents the root
+        box/category in the content hierarchy exposed by the plugin.
+      </listitem>
+    </itemizedlist>
+  </para>
+
+  <para>
+    Please, check <link linkend="media-source-plugins-common-considerations">
+      Common considerations for Search, Browse and Query implementations</link>
+    for more information on how to implement Browse operations properly.
+  </para>
+
+  <para>
+    Examples of plugins implementing browse functionality are
+    grl-jamendo, grl-filesystem or grl-upnp among others.
+  </para>
+
+</section>
+
+<section id="media-source-plugins-query">
+  <title>Implementing Query</title>
+  
+  <para>
+    Implementation of the "query" method is optional, but at least one
+    of Search, Browse and Query are expected to be implemented.
+  </para>
+
+  <para>
+    This method provides plugin developers with means to expose
+    service-specific functionality that cannot be achieved
+    through regular Browse and Search operations.
+  </para>
+
+  <para>
+    This method operates just like the Search method, but the text
+    parameter does not represent a list of keywords to search for,
+    instead, its meaning is plugin specific and defined by the plugin
+    developer. 
+  </para>
+
+  <para>
+    Normally, Query implementations involve parsing and decoding this
+    input string into something meaningful for the media provider
+    (a specific operation with its parameters).
+  </para>
+
+  <para>
+    Usually, Query implementations are intended to provide advanced
+    filtering capabilities and similar features that make use of
+    specific features of the service that cannot be
+    exposed through more service agnostic APIs, like Search or
+    Browse. For example, a plugin that provides media content
+    stored in a database can implement Query to give users the 
+    possibility to execute SQL queries directly, by encoding the
+    SQL commands in this input string, giving a lot of flexibility
+    in how they access the content stored in the database in
+    exchange for writing plugin-specific code in the application.
+  </para>
+
+  <para>
+    The example below shows the case of a plugin implementing
+    Query to let the user specify filters directly in SQL
+    format for additional flexibility.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->query = grl_foo_source_query;
+}
+
+static void
+grl_foo_source_query (GrlMediaSource *source, GrlMediaSourceQuerySpec *qs)
+{
+  const gchar *sql_filter;
+  GList *results;
+  GrlMedia *media;
+  gint count;
+
+  /* qs->text is expected to contain a suitable SQL filter */
+  sql_query = prepare_sql_with_custom_filter (qs->text, qs->skip, qs->count);
+  
+  /* Execute the resulting SQL query, which incorporates
+     the filter provided by the user */
+  results = execute_sql (sql_query);
+
+  /* For each result obtained, invoke the user callback as usual */
+  count = g_list_length (results);
+
+  if (count == 0) {
+    /* Signal "no results" */
+    qs->callback (qs->source, qs->operation_id,
+                  NULL, 0, qs->user_data, NULL);
+  } else {
+    while (media = next_result (&results))
+      qs->callback (qs->source,       /* Source emitting the data */
+                    qs->operation_id, /* Operation identifier */
+                    media,            /* Media that matched the query */
+                    --count,          /* Remaining count */
+                    qs->user_data,    /* User data for the callback */
+                    NULL);            /* GError instance (if an error occurred) */
+  }
+}
+]]>
+  </programlisting>
+
+  <para>
+    Please, check <link linkend="media-source-plugins-common-considerations">
+      Common considerations for Search, Browse and Query implementations</link>
+    for more information on how to implement Query operations properly.
+  </para>
+
+  <para>
+    Examples of plugins implementing Query are grl-jamendo,
+    grl-upnp or grl-bookmarks among others.
+  </para>
+</section>
+
+<section id="media-source-plugins-common-considerations">
+  <title>Common considerations for Search, Browse and Query implementations</title>
+
+  <para>
+    <itemizedlist>
+      <listitem>Making operations synchronous would block the client
+        application while the operation is executed, so providing
+        a non-blocking implementation is mostly mandatory for most practical
+        purposes.</listitem>
+      <listitem>
+        Grilo invokes plugin operations in idle callbacks to ensure that control
+        is returned to the client as soon as possible. Still, plugin developers
+        are encouraged to write efficient code that avoids blocking as much as
+        possible, since this good practice will make applications behave
+        smoother, granting a much better user experience. Use of threads in
+        plugin code is not recommended, instead, splitting the work to do in 
+        chunks using the idle loop is encouraged.
+      </listitem>
+      <listitem>Creating GrlMedia instances is easy, depending on the type of
+        media you should instantiate one of the GrlMedia subclasses (GrlMediaImage,
+        GrlMediaVideo, GrlMediaAudio or GrlMediaBox), and then use the API to
+        set the corresponding data. Check the <link linkend="GrlData">GrlData</link>
+        hierarchy in the API reference for more details.
+      </listitem>
+      <listitem>The remaining count parameter present in the callbacks is intended
+        to provide the client with an <emphasis>estimation</emphasis> of how many
+        more results will come after the current one as part of the same operation.</listitem>
+      <listitem>Finalization of the operation must <emphasis>always</emphasis>
+        be signaled by invoking the user callback with remaining count set to 0,
+        even on error conditions.
+      </listitem>
+      <listitem>Plugin developers must ensure that all operations
+        end by invoking the user callback with the remaining count parameter
+        set to 0, and that this is done only once per operation. This
+        behavior is expected and must be guaranteed by the plugin developer.</listitem>
+      <listitem>
+        Once the user callback has been invoked with the remaining count
+        parameter set to 0, the operations is considered finished and the
+        plugin developer must <emphasis>never</emphasis>
+        invoke the user callback again for that operation again.
+      </listitem>
+      <listitem>
+        In case of error, the plugin developer must invoke the user
+        callback like this:
+        <itemizedlist>
+          <listitem>Set the last parameter to a non-NULL GError instance.</listitem>
+          <listitem>Set the media parameter to NULL.</listitem>
+          <listitem>Set the remaining count parameter to 0.</listitem>
+        </itemizedlist>
+        The plugin developer is responsible for releasing the error once
+        the user callback is invoked.
+      </listitem>
+      <listitem>It is possible to finalize the operation with a NULL
+        media and remaining count set to 0 if that is convenient for the
+        plugin developer.
+      </listitem>
+      <listitem>
+        Returned GrlMedia objects are owned by the client and should not be
+        freed by the plugin.
+      </listitem>
+      <listitem>
+        The list of metadata information requested by the client is
+        available in the "keys" field of the Spec structure. Typically plugin
+        developers don't have to care about the list of keys requested and
+        would just resolve all metadata available. The only situation in which
+        the plugin developer should  check the specific list of keys requested
+        is when there are keys that are particularly expensive to
+        resolve, in these cases the plugin should only resolve these keys if
+        the user has indeed requested that information.
+      </listitem>
+    </itemizedlist>
+  </para>
+</section>
+
+
+<section id="media-source-plugins-metadata">
+  <title>Implementing Metadata</title>
+  
+  <para>
+    Implementation of the "metadata" method is not mandatory
+    but would be usually expected by application developers.
+  </para>
+
+  <para>
+    The purpose of the metadata method is to provide additional
+    metadata for GrlMedia objects produced by the media source.
+  </para>
+
+  <para>
+    Typically, the use case for Metadata operations is applications
+    obtaining a list of GrlMedia objects by executing a Browse,
+    Search or Query operation , requesting limited metadata (for
+    performance reasons), and then requesting additional metadata
+    for specific items selected by the user.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->metadata = grl_foo_source_metadata;
+}
+
+static void
+foo_media_info_cb (gchar *xml, GrlMediaSourceMetadataSpec *ms)
+{
+  /* This resolves metadata for keys "ms->keys" from "xml" and
+     stores them in "ms->media" */
+  resolve_metadata_from_xml (ms->media, ms->keys, xml);
+
+  /* Send updated ms->media back to the user */
+  ms->callback (ms->source, ms->metadata_id, ms->media, ms->user_data, NULL);
+}
+
+static void
+grl_foo_source_metadata (GrlMediaSource *source, GrlMediaSourceMetadataSpec *ms)
+{
+  const gchar *media_id;
+
+  media_id = grl_media_get_id (ms->media);
+
+  foo_media_info =
+    g_strdup_printf("http://media.foo.com/media-info/%s";, media_id);
+
+  /* This executes an async http query and then invokes
+     foo_metadata_cb with the response */
+     foo_execute_metadata_async (foo_media_info, ms);
+}
+]]>
+  </programlisting>
+
+  <para>
+    Some considerations that plugin developers should take into account:
+    <itemizedlist>
+      <listitem>
+        Clients invoke this method passing the GrlMedia object that
+        they want to update (ms->media). Plugin developers should resolve the
+        requested metadata (ms->keys) and store it in that GrlMedia
+        object.
+      </listitem>
+      <listitem>
+        Just like in other APIs, implementation of this method is expected
+        to be asynchronous to avoid blocking the user code.
+      </listitem>
+    </itemizedlist>
+  </para>
+  
+  <para>
+    Examples of plugins implementing Metadata are grl-youtube,
+    grl-upnp or grl-jamendo among others.
+  </para>
+</section>
+
+
+<section id="media-source-plugins-store">
+  <title>Implementing Store</title>
+  
+  <para>
+    Implementation of the "store" method is optional.
+  </para>
+
+  <para>
+    The Store method is used to push new content to the media source.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->store = grl_foo_source_store;
+}
+
+static void
+grl_foo_source_store (GrlMediaSource *source, GrlMediaSourceStoreSpec *ss)
+{
+  const gchar *title;
+  const gchar *uri;
+  const gchar *parent_id;
+  guint row_id;
+
+  /* We get the id of the parent container where we want
+     to put the new content */
+  parent_id = grl_media_get_id (GRL_MEDIA (parent));
+
+  /* We get he metadata of the media we want to push, in this case
+     only URI and Title */
+  uri = grl_media_get_uri ();
+  title = grl_media_get_title ();
+  
+  /* Push the data to the media provider (in this case a database) */
+  row_id = run_sql_insert (parent_id, uri, title);
+
+  /* Set the media id in the GrlMedia object */
+  grl_media_set_id (ss->media, row_id_to_media_id (row_id));
+
+  /* Inform the user that the operation is done (NULL error means everything
+     was ok */
+  ss->callback (ss->source, ss->parent, ss->media, ss->user_data, NULL);
+}
+]]>
+  </programlisting>
+
+  <para>
+    Some considerations that plugin developers should take into account:
+    <itemizedlist>
+      <listitem>
+        After successfully storing the media, the method should assign
+        a proper media id to it before invoking the user callback.
+      </listitem>
+    </itemizedlist>
+  </para>
+
+  <para>
+    Examples of plugins implementing Store are grl-bookmarks or
+    grl-podcasts.
+  </para>
+</section>
+
+
+<section id="media-source-plugins-remove">
+  <title>Implementing Remove</title>
+  
+  <para>
+    Implementation of the "remove" method is optional.
+  </para>
+
+  <para>
+    The Remove method is used to remove content from the media source.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->remove = grl_foo_source_remove;
+}
+
+static void
+grl_foo_source_remove (GrlMediaSource *source, GrlMediaSourceRemoveSpec *rs)
+{
+  /* Remove the data from the media provider (in this case a database) */
+  run_sql_delete (ss->media_id);
+
+  /* Inform the user that the operation is done (NULL error means everything
+     was ok */
+  rs->callback (rs->source, rs->media, rs->user_data, NULL);  
+}
+]]>
+  </programlisting>
+
+  <para>
+    Examples of plugins implementing Remove are grl-bookmarks or
+    grl-podcasts.
+  </para>
+</section>
+
+<section id="media-source-plugins-media-from-uri">
+  <title>Implementing Media from URI</title>
+
+  <para>Implementation of the "media_from_uri" method is optional.</para>
+
+  <para>
+    Some times clients have access to the URI of the media, and they
+    want to retrieve metadata for it. A couple of examples where this may
+    come in handy: A file system browser that needs to obtain additional
+    metadata for a particular media item located in the filesystem. A browser
+    plugin that can obtain additional metadata for a media item given its
+    URL. In these cases we know the URI of the media, but we need to
+    create a GrlMedia object representing it.
+  </para>
+
+  <para>
+    Plugins that want to support URI to GrlMedia conversions must implement
+    the "test_media_from_uri" and "media_from_uri" methods.
+  </para>
+
+  <para>
+    The method "test_media_from_uri" should return TRUE if, upon inspection of
+    the media URI, the plugin decides that it can convert it to a GrlMedia
+    object. For example, a YouTube plugin would check that the URI of the media
+    is  a valid YouTube URL. This method is asynchronous and should not block.
+    If the plugin cannot decide if it can or cannot convert the URI to a 
+    GrlMedia object by inspecting the URI without doing blocking operations,
+    it should return TRUE. This method is used to discard efficiently plugins
+    that cannot resolve the media.
+  </para>
+
+  <para>
+    The method "media_from_uri" is used to do the actual conversion from the
+    URI to the GrlMedia object.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->test_media_from_uri = grl_foo_source_test_media_from_uri;
+  media_class->media_from_uri = grl_foo_source_media_from_uri;
+}
+
+static gboolean
+grl_filesystem_test_media_from_uri (GrlMediaSource *source,
+                                    const gchar *uri)
+{
+  if (strstr (uri, "http://media.foo.com/media-info/";) == uri) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static void
+grl_filesystem_media_from_uri (GrlMediaSource *source,
+                               GrlMediaSourceMediaFromUriSpec *mfus)
+{
+  gchar *media_id;
+  GrlMedia *media;
+
+  media_id = get_media_id_from_uri (mfus->uri);
+  media = create_media_from_id (media_id);
+  mfus->callback (source, mfus->media_from_uri_id, media, mfus->user_data, NULL);
+  g_free (media_id);
+}
+]]>
+  </programlisting>
+
+  <para>
+    Some considerations that plugin developers should take into account:
+    <itemizedlist>
+      <listitem>
+        Typically "media_from_uri" involves a blocking operation, and hence
+        its implementation should be asynchronous.
+      </listitem>
+    </itemizedlist>
+
+    <para>
+      Examples of plugins implementing "media_from_uri" are grl-filesystem
+      or grl-youtube.
+    </para>
+  </para>
+</section>
+
+<section id="media-source-plugins-change_notification">
+  <title>Notifying changes</title>
+
+  <para>
+    Source can signal clients when available media content has been
+    changed. This is an optional feature.
+  </para>
+
+  <para>
+    Plugins supporting content change notification must implement
+    "notify_change_start" and "notify_change_stop", which let the
+    user start or stop content change notification at will.
+  </para>
+
+  <para>
+    Once users have activated notifications by invoking
+    "notify_change_start", media sources should communicate
+    any changes detected by calling grl_media_source_notify_change_list
+    with a list of the media items changed.
+  </para>
+
+  <para>
+    Upon calling "notify_changes_stop" the plugin must stop
+    communicating changes until "notify_changes_start" is
+    called again.
+  </para>
+
+  <programlisting role="C">
+    <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  media_class->notify_change_start = grl_foo_source_notify_change_start;
+  media_class->notify_change_stop = grl_foo_source_notify_change_stop;
+}
+
+static void
+content_changed_cb (GList *changes)
+{
+  GPtrArray *changed_medias;
+
+  changed_medias = g_ptr_array_sized_new (g_list_length (changes));
+  while (media = next_media_from_changes (&changes)) {
+    g_ptr_array_add (changed_medias, media);
+  }
+
+  grl_media_source_notify_change_list (source,
+                                       changed_medias,
+                                       GRL_CONTENT_CHANGED,
+                                       FALSE);
+}
+
+static gboolean
+grl_foo_source_notify_change_start (GrlMediaSource *source,
+                                    GError **error)
+{
+  GrlFooMediaSource *foo_source;
+
+  /* Listen to changes in the media content provider */
+  foo_source = GRL_FOO_MEDIA_SOURCE (source);
+  foo_source->listener_id = foo_subscribe_listener_new (content_changed_cb);
+
+  return TRUE;
+}
+
+static gboolean
+grl_foo_source_notify_change_stop (GrlMediaSource *source,
+                                   GError **error)
+{
+  GrlFooMediaSource *foo_source;
+
+  /* Stop listening to changes in the media content provider */
+  foo_source = GRL_FOO_MEDIA_SOURCE (source);
+  foo_listener_destroy (foo_source->listener_id);
+
+  return TRUE;
+}
+]]>
+  </programlisting>
+  
+  <para>
+    Please check the 
+    <link linkend="GrlMediaSource">GrlMediaSource</link> API reference
+    for more details on how grl_media_source_notify_change_list
+    should be used.
+  </para>
+
+  <para>Examples of plugins implementing change notification are
+    gr-upnp and grl-tracker among others
+  </para>
+</section>
+</section>
diff --git a/doc/grilo/quick-start-plugins-metadata-sources.xml b/doc/grilo/quick-start-plugins-metadata-sources.xml
new file mode 100644
index 0000000..53d0c04
--- /dev/null
+++ b/doc/grilo/quick-start-plugins-metadata-sources.xml
@@ -0,0 +1,392 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"; [
+<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+
+
+<section>
+  <section id="metadata-source-plugins-intro">
+    <title>Introduction</title>
+
+    <para>
+      Metadata source plugins provide access to additional metadata information.
+    </para>
+
+    <para>
+      Unlike media sources, metadata sources do not provide access to media content,
+      but additional metadata information about content that was provided by
+      media sources.
+    </para>
+
+    <para>
+      An example of a metadata source would be one which is able to provide
+      thumbnail information for local audio content from an online service.
+    </para>
+
+    <para>
+      Media sources extend GrlMetadataSource, so they are also metadata
+      sources.
+    </para>
+
+    <para>
+      Typically, users interact with metadata sources to:
+      <itemizedlist>
+        <listitem>Resolve additional metadata for a particular media item.</listitem>
+        <listitem>Update metadata for a particular media item.</listitem>
+      </itemizedlist>
+    </para>
+  </section>
+
+  <section id="metadata-source-plugins-basics">
+    <title>Registering the plugin</title>
+
+    <para>
+      Registering a new metadata source plugin is done by following the same
+      procedure as for media source plugins, except that they must extend
+      GrlMetadataSource. Please, check 
+      <link linkend="media-source-plugins-basics">Registering Media Source Plugins</link>
+      for details.
+    </para>
+
+    <para>
+      Metadata source plugins must also implement "supported_keys", and optionally
+      "slow_keys". Please check 
+      <link linkend="media-source-plugins-supported-keys">Implementing Supported Keys</link>
+      and
+      <link linkend="media-source-plugins-slow-keys">Implementing Slow Keys</link>        
+      respectively for further details.
+    </para>
+  </section>
+
+
+  <section id="metadata-source-plugins-resolve">
+    <title>Implementing Resolve</title>
+    
+    <para>
+      An implementation of the "resolve" method is mandatory for metadata
+      plugins to work.
+    </para>
+
+    <para>
+      Resolve operations are issued in order to grab additional information
+      on a given media (GrlMedia).
+    </para>
+
+    <para>
+      Typically, implementing Resolve implies inspecting the metadata 
+      already known for that media and use that information to gain access
+      to new information. For example, a plugin can use the artist and album
+      information of a given GrlMediaAudio item to obtain additional information,
+      like the album cover thumbnail.
+    </para>
+
+    <para>
+      Plugins implementing "resolve" must also implement "may_resolve". The
+      purpose of this method is to analyze if the GrlMedia contains enough
+      metadata to enable the plugin to extract the additional metadata
+      requested.
+    </para>
+
+    <programlisting role="C">
+      <![CDATA[
+/* In this example we assume a plugin that can resolve thumbnail 
+   information for audio items given that we have artist and album
+   information available  */
+
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+  metadata_class->may_resolve = grl_foo_source_may_resolve;
+  metadata_class->resolve = grl_foo_source_resolve;
+}
+
+static gboolean
+grl_foo_source_may_resolve (GrlMetadataSource *source,
+                            GrlMedia *media,
+                            GrlKeyID key_id,
+                            GList **missing_keys)
+{
+  gboolean needs_artist = FALSE;
+  gboolean needs_album  = FALSE;
+
+  /* We only support thumbnail resolution */
+  if (key_id != GRL_METADATA_KEY_THUMBNAIL)
+    return FALSE;
+
+  /* We only support audio items */
+  if (media) {
+    if (!GRL_IS_MEDIA_AUDIO (media))
+      return FALSE;
+
+    /* We need artist information available */
+    if (grl_media_audio_get_artist (GRL_MEDIA_AUDIO (media)) == NULL) {
+      if (missing_keys)
+        *missing_keys = g_list_add (*missing_keys,
+                                    GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ARTIST));
+      needs_artist = TRUE;
+    }
+
+    /* We need album information available */
+    if (grl_media_audio_get_album (GRL_MEDIA_AUDIO (media)) == NULL)) {
+      if (missing_keys)
+        *missing_keys = g_list_add (*missing_keys,
+                                    GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ALBUM));
+      needs_album = TRUE;
+    }
+  }
+
+  if (needs_album || needs_artist)
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+grl_foo_source_resolve (GrlMetadataSource *source,
+                        GrlMetadataSourceResolveSpec *rs)
+{
+  const gchar *album;
+  const gchar *artist,
+  gchar *thumb_uri;
+  const GError *error = NULL;
+
+  if (contains_key (rs->keys, GRL_METADATA_KEY_THUMBNAIL) {
+    artist = grl_media_audio_get_artist (GRL_MEDIA_AUDIO (rs->media)); 
+    album = grl_media_audio_get_album (GRL_MEDIA_AUDIO (rs->media)); 
+    if (artist && album) {
+      thumb_uri = resolve_thumb_uri (artist, album);
+      grl_media_set_thumbnail (rs->media, thumb_uri);
+    } else {
+      error = g_error_new (GRL_CORE_ERROR,
+                           GRL_CORE_ERROR_RESOLVE_FAILED,
+                           "Can't resolve thumbnail, artist and album not known");
+    }
+  } else {
+      error = g_error_new (GRL_CORE_ERROR,
+                           GRL_CORE_ERROR_RESOLVE_FAILED,
+                           "Can't resolve requested keys");
+  }
+
+  rs->callback (source, rs->resolve_id, rs->media, rs->user_data, error);
+
+  if (error)
+    g_error_free (error);
+}
+]]>
+    </programlisting>
+
+    <para>
+      Some considerations that plugin developers should take into account:
+      <itemizedlist>
+        <listitem>
+          The method "may_resolve" is synchronous, should be fast and
+          never block. If the plugin cannot confirm if it can resolve the
+          metadata requested without doing blocking operations then it should
+          return TRUE. Then, when "resolve" is invoked further checking 
+          can be done.
+        </listitem>
+        <listitem>
+          Typically "resolve" involves a blocking operation, and hence
+          its implementation should be asynchronous.
+        </listitem>
+      </itemizedlist>
+    </para>
+
+    <para>
+      Examples of plugins implementing "resolve" are grl-lastfm-albumart
+      or grl-local-metadata among others.
+    </para>
+  </section>
+
+  <section id="metadata-source-plugins-set-metadata">
+    <title>Implementing Set Metadata</title>
+    
+    <para>
+      Implementing "set_metadata" is optional.
+    </para>
+
+    <para>
+      Some plugins may provide users with the option of updating the metadata
+      available for specific media items. For example, a plugin may store user
+      metadata like the last time that a certain media resource was played
+      or its play count. These metadata properties do not make sense if 
+      applications do not have means to change and update their values.
+    </para>
+
+    <para>
+      Plugins that support this feature must implement two methods:
+      <itemizedlist>
+        <listitem>
+          <emphasis>writable_keys:</emphasis> just like "supported_keys"
+          or "slow_keys", it is a declarative method, intended to provide
+          information on what keys supported by the plugin are writable, that is,
+          their values can be changed by the user.
+        </listitem>
+        <listitem>
+          <emphasis>set_metadata:</emphasis> which is the method used
+          by clients to update metadata values for specific keys.
+        </listitem>
+      </itemizedlist>
+    </para>
+
+    <programlisting role="C">
+      <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+  metadata_class->writable_keys = grl_foo_source_writable_keys;
+  metadata_class->set_metadata = grl_foo_source_set_metadata;
+}
+
+static const GList *
+grl_foo_source_writable_keys (GrlMetadataSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING,
+                                      GRL_METADATA_KEY_PLAY_COUNT,
+                                      GRL_METADATA_KEY_LAST_PLAYED,
+                                      NULL);
+  }
+  return keys;
+}
+
+static void
+grl_foo_source_set_metadata (GrlMetadataSource *source,
+			     GrlMetadataSourceSetMetadataSpec *sms)
+{
+  GList *iter;
+  const gchar *media_id;
+  GList *failed_keys = NULL;
+
+  /* 'sms->media' contains the media with updated values */
+  media_id = grl_media_get_id (sms->media);
+
+  /* Go through all the keys that need update ('sms->keys'), take
+     the new values (from 'sms->media') and update them in the
+     media provider  */
+  iter = sms->keys;
+  while (iter) {
+    GrlKeyID key = GRLPOINTER_TO_KEYID (iter->data);
+    if (!foo_update_value_for_key (sms->media, key)) {
+      /* Save a list with keys that we failed to update */
+      failed_keys = g_list_prepend (failed_keys, iter->data);
+    }
+    iter = g_list_next (iter);
+  }
+
+  /* We are done, invoke user callback to signal client */
+  sms->callback (sms->source, sms->media, failed_keys, sms->user_data, NULL);
+  g_list_free (failed_keys);
+}
+]]>
+    </programlisting>
+
+    <para>
+      Some considerations that plugin developers should take into account:
+      <itemizedlist>
+        <listitem>
+          Typically, updating metadata keys in the media provider would involve
+          one or more blocking operations, so asynchronous implementations
+          of "set_metadata" should be considered.
+        </listitem>
+        <listitem>
+          Some media providers may allow for the possibility of updating
+          multiple keys in just one operation. 
+        </listitem>
+        <listitem>
+          The user callback for "set_metadata" receives a list with all the keys
+          that failed to be updated, which the plugin should free after calling
+          the user callback.
+        </listitem>
+      </itemizedlist>
+    </para>
+
+    <para>
+      Examples of plugins implementing "set_metadata" are grl-metadata-store or
+      grl-tracker.
+    </para>
+  </section>
+
+  <section id="metadata-source-plugins-cancel">
+    <title>Cancelling ongoing operations</title>
+    
+    <para>
+      Implementing the "cancel" method is optional. This method provided means
+      for application developers to cancel ongoing operations on metadata
+      sources (and hence, also in media sources).
+    </para>
+
+    <para>
+      The "cancel" method receives the identifier of the operation to be
+      cancelled.
+    </para>
+
+    <para>
+      Typically, plugin developers would implement cancellation support
+      by storing relevant information for the cancellation process
+      along with the operation data when this is started, and then
+      retrieving this information when a cancellation request is received.
+    </para>
+
+    <para>
+      Grilo provides plugin developers with API to attach arbitrary data
+      to a certain operation given its identifier. These APIs are:
+      <itemizedlist>
+        <listitem>grl_metadata_source_set_operation_data</listitem>
+        <listitem>grl_metadata_source_get_operation_data</listitem>
+      </itemizedlist>
+      See the API reference documentation for
+      <link linkend="GrlMetadataSource">GrlMetadataSource</link> for
+      more details.
+    </para>
+
+    <programlisting role="C">
+      <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlMediaSourceClass *media_class = GRL_MEDIA_SOURCE_CLASS (klass);
+  GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+  media_class->search = grl_foo_source_search;
+  metadata_class->cancel = grl_foo_source_cancel;
+}
+
+static void
+grl_foo_source_search (GrlMediaSource *source,
+                       GrlMediaSourceSearchSpec *ss)
+{
+  ...
+  gint op_handler = foo_service_search_start (ss->text, ...);
+  grl_metadata_source_set_operation_data (GRL_METADATA_SOURCE (source),
+                                          ss->operation_id,
+                                          GINT_TO_POINTER (op_handler));
+  ...
+}
+
+static void
+grl_foo_source_cancel (GrlMetadataSource *source, guint operation_id)
+{
+  gint op_handler;
+  
+  op_handler =
+    GPOINTER_TO_INT (grl_metadata_source_get_operation_data (source,
+                                                             operation_id));
+  if (op_handler > 0) {
+    foo_service_search_cancel (op_handler);
+  }
+}
+]]>
+    </programlisting>
+
+    <para>
+      Some examples of plugins implementing cancellation support are
+      grl-youtube, grl-jamendo or grl-filesystem, among others.
+    </para>
+  </section>
+</section>
+
diff --git a/doc/grilo/quick-start-plugins-testing.xml b/doc/grilo/quick-start-plugins-testing.xml
new file mode 100644
index 0000000..2f6c9ab
--- /dev/null
+++ b/doc/grilo/quick-start-plugins-testing.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"; [
+<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+
+<section id="media-source-testing-plugins">
+  <title>Testing your plugins</title>
+
+  <para>
+    Grilo ships a GTK+ test user interface called <emphasis>grilo-test-ui</emphasis>
+    that can be used to test new plugins. This simple playground application can be
+    found in the 'grilo' core source code under tools/grilo-test-ui/. If you have
+    Grilo installed on your system, you may have this application installed as
+    well.
+  </para>
+
+  <para>
+    This application loads plugins from the default plugin installation directory
+    in your system or, alternatively, by inspecting the GRL_PLUGIN_PATH environment
+    variable, which can be set to contain a list of directories where Grilo
+    should look for plugins.
+  </para>
+
+  <para>
+    Once the plugin library is visible to Grilo one only has to start the
+    grilo-test-ui application and it will load it along with other Grilo
+    plugins available in the system.
+  </para>
+
+  <para>
+    In case there is some problem with the initialization of the plugin it should
+    be logged on the console. Remember that you can control the amount of 
+    logging done by Grilo through the GRL_DEBUG environment variable. You
+    may want to set this variable to do full logging, in which case
+    you should type this in your console:
+  </para>
+
+  <programlisting>   
+$ export GRL_DEBUG="*:*"
+  </programlisting>   
+
+  <para>
+    If you want to focus only on logging the plugin loading process, configure
+    Grilo to log full details from the plugin registry module alone
+    by doing this instead:
+  </para>
+
+  <programlisting>   
+$ export GRL_DEBUG="plugin-registry:*"
+  </programlisting>   
+
+  <para>
+    In case your plugin has been loaded successfully you should see something like
+    this in the log:
+  </para>
+
+  <programlisting> 
+(lt-grilo-test-ui:14457): Grilo-DEBUG: [plugin-registry] grl-plugin-registry.c:188: Plugin rank [plugin-id]' : 0
+(lt-grilo-test-ui:14457): Grilo-DEBUG: [plugin-registry] grl-plugin-registry.c:476: New source available: [source-id]
+(lt-grilo-test-ui:14457): Grilo-DEBUG: [plugin-registry] grl-plugin-registry.c:683: Loaded plugin '[plugin-id]' from '[plugin-file-absolute-path.so]'
+  </programlisting>
+
+  <para>
+    If your plugin is a Media Source (not a Metadata Source) you should be able
+    to see it in the user interface of grilo-test-ui like this:
+    <itemizedlist>
+      <listitem>
+        If the plugin implements Browse you should see the media source objects
+        spawned by the plugin in the list shown in the main view. You can
+        browse the plugin by double-clicking on any of its sources.
+      </listitem>
+      <listitem>
+        If the plugin implements Search you should see the media source objects
+        spawned by the plugin in the combo box next to the "Search" button.
+        You can now search content by selecting the media source you want to test
+        in the combo, inputting a search text in the text entry right next to it
+        and clicking the Search button.
+      </listitem>
+      <listitem>
+        If the plugin implements query you should see the media source objects
+        spawned by the plugin in the combo box next to the "Query" button.
+        You can now query content by selecting the media source you want to test
+        in the combo, inputting the plugin-specific query string in the text 
+        entry right next to it and clicking the Query button.
+      </listitem>
+    </itemizedlist>
+  </para>
+  
+  <para>
+    If your plugin is a Metadata Source then you should test it by doing
+    a Browse, Search or Query operation in some other Media Source available
+    and then click on any of the media items showed as result. By doing this
+    grilo-test-ui will execute a Metadata operation which would use any
+    available metadata plugins to gather as much information as possible.
+    Available metadata obtained for the selected item will be shown in the
+    right pane for users to inspect.
+  </para>
+</section>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]