[grilo] doc: Improve documentation



commit 157546f8612e0b061edcec32fd2e64c7c6ee647f
Author: Juan A. Suarez Romero <jasuarez igalia com>
Date:   Sat Jul 14 19:44:24 2012 +0000

    doc: Improve documentation

 doc/grilo/Makefile.am                  |    5 +-
 doc/grilo/grilo-docs.sgml              |   13 +-
 doc/grilo/grilo-sections.txt           |  149 ++---
 doc/grilo/grilo.types                  |    2 -
 doc/grilo/plugins-media-sources.xml    | 1171 --------------------------
 doc/grilo/plugins-metadata-sources.xml |  398 ---------
 doc/grilo/plugins-sources.xml          | 1433 ++++++++++++++++++++++++++++++++
 libs/net/grl-net-wc.h                  |    5 +-
 src/grl-caps.c                         |    7 +-
 src/grl-multiple.c                     |    2 +-
 src/grl-operation-options.c            |    8 +-
 src/grl-plugin-registry.c              |    7 +-
 src/grl-source.c                       |    3 +-
 src/grl-source.h                       |    5 +-
 14 files changed, 1514 insertions(+), 1694 deletions(-)
---
diff --git a/doc/grilo/Makefile.am b/doc/grilo/Makefile.am
index 7157aa6..cd0b13c 100644
--- a/doc/grilo/Makefile.am
+++ b/doc/grilo/Makefile.am
@@ -59,7 +59,7 @@ EXTRA_HFILES=
 # Header files to ignore when scanning. Use base file name, no paths
 # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
 IGNORE_HFILES=config.h \
-	grl-media-plugin-priv.h \
+	grl-plugin-priv.h \
 	grl-operations-priv.h \
 	grl-sync-priv.h \
 	grl-metadata-key-priv.h \
@@ -76,8 +76,7 @@ content_files=overview.xml	\
 	quick-start.xml \
 	environment-setup.xml \
 	writing-apps.xml \
-	plugins-media-sources.xml \
-	plugins-metadata-sources.xml \
+	plugins-sources.xml \
 	plugins-testing.xml
 
 # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
diff --git a/doc/grilo/grilo-docs.sgml b/doc/grilo/grilo-docs.sgml
index e16a904..d2b663d 100644
--- a/doc/grilo/grilo-docs.sgml
+++ b/doc/grilo/grilo-docs.sgml
@@ -42,12 +42,8 @@
       <title>Writing plugins for Grilo</title>
       <section id="writing-plugins">
         <section id="media-source-plugins">
-          <title>Media Source plugins</title>
-          <xi:include href="plugins-media-sources.xml"/>
-        </section>
-        <section id="metadata-source-plugins">
-          <title>Metadata Source plugins</title>
-          <xi:include href="plugins-metadata-sources.xml"/>
+          <title>Source plugins</title>
+          <xi:include href="plugins-sources.xml"/>
         </section>
         <section id="testing-plugins">
           <title>Testing your plugins</title>
@@ -74,9 +70,8 @@
 
     <chapter id="sources">
       <title>Data sources</title>
-      <xi:include href="xml/grl-media-plugin.xml"/>
-      <xi:include href="xml/grl-metadata-source.xml"/>
-      <xi:include href="xml/grl-media-source.xml"/>
+      <xi:include href="xml/grl-plugin.xml"/>
+      <xi:include href="xml/grl-source.xml"/>
     </chapter>
 
     <chapter id="caps-options">
diff --git a/doc/grilo/grilo-sections.txt b/doc/grilo/grilo-sections.txt
index 0fe16be..01b9144 100644
--- a/doc/grilo/grilo-sections.txt
+++ b/doc/grilo/grilo-sections.txt
@@ -34,7 +34,6 @@ grl_plugin_get_author
 grl_plugin_get_site
 grl_plugin_get_id
 grl_plugin_get_filename
-grl_plugin_get_rank
 grl_plugin_get_info_keys
 grl_plugin_get_info
 grl_plugin_get_sources
@@ -54,7 +53,22 @@ GrlPluginPrivate
 <FILE>grl-source</FILE>
 <TITLE>GrlSource</TITLE>
 GrlSource
+GrlResolutionFlags
+GrlWriteFlags
 GrlSupportedOps
+GrlSourceChangeType
+GrlSourceResolveSpec
+GrlSourceStoreMetadataSpec
+GrlSourceBrowseSpec
+GrlSourceSearchSpec
+GrlSourceQuerySpec
+GrlSourceStoreSpec
+GrlSourceRemoveSpec
+GrlSourceMediaFromUriSpec
+GrlSourceResolveCb
+GrlSourceResultCb
+GrlSourceStoreCb
+GrlSourceRemoveCb
 GrlSourceClass
 grl_source_supported_operations
 grl_source_supported_keys
@@ -64,6 +78,30 @@ grl_source_get_id
 grl_source_get_name
 grl_source_get_description
 grl_source_get_caps
+grl_source_may_resolve
+grl_source_resolve
+grl_source_resolve_sync
+grl_source_store_metadata
+grl_source_store_metadata_sync
+grl_source_browse
+grl_source_browse_sync
+grl_source_search
+grl_source_search_sync
+grl_source_query
+grl_source_query_sync
+grl_source_store
+grl_source_store_sync
+grl_source_remove
+grl_source_remove_sync
+grl_source_set_auto_split_threshold
+grl_source_get_auto_split_threshold
+grl_source_test_media_from_uri
+grl_source_get_media_from_uri
+grl_source_get_media_from_uri_sync
+grl_source_notify_change_start
+grl_source_notify_change_stop
+grl_source_notify_change_list
+grl_source_notify_change
 <SUBSECTION Standard>
 GRL_SOURCE
 GRL_IS_SOURCE
@@ -77,84 +115,6 @@ GrlSourcePrivate
 </SECTION>
 
 <SECTION>
-<FILE>grl-metadata-source</FILE>
-<TITLE>GrlMetadataSource</TITLE>
-GrlMetadataResolutionFlags
-GrlMetadataWritingFlags
-GrlMetadataSource
-GrlMetadataSourceResolveCb
-GrlMetadataSourceSetMetadataCb
-GrlMetadataSourceResolveSpec
-GrlMetadataSourceSetMetadataSpec
-GrlMetadataSourceClass
-grl_metadata_source_may_resolve
-grl_metadata_source_resolve
-grl_metadata_source_resolve_sync
-grl_metadata_source_set_metadata
-grl_metadata_source_set_metadata_sync
-<SUBSECTION Standard>
-GRL_METADATA_SOURCE
-GRL_IS_METADATA_SOURCE
-GRL_TYPE_METADATA_SOURCE
-grl_metadata_source_get_type
-GRL_METADATA_SOURCE_CLASS
-GRL_IS_METADATA_SOURCE_CLASS
-GRL_METADATA_SOURCE_GET_CLASS
-<SUBSECTION Private>
-GrlMetadataSourcePrivate
-</SECTION>
-
-<SECTION>
-<FILE>grl-media-source</FILE>
-<TITLE>GrlMediaSource</TITLE>
-GrlMediaSourceChangeType
-GrlMediaSource
-GrlMediaSourceResultCb
-GrlMediaSourceMetadataCb
-GrlMediaSourceStoreCb
-GrlMediaSourceRemoveCb
-GrlMediaSourceBrowseSpec
-GrlMediaSourceSearchSpec
-GrlMediaSourceQuerySpec
-GrlMediaSourceMetadataSpec
-GrlMediaSourceStoreSpec
-GrlMediaSourceRemoveSpec
-GrlMediaSourceMediaFromUriSpec
-GrlMediaSourceClass
-grl_media_source_browse
-grl_media_source_browse_sync
-grl_media_source_search
-grl_media_source_search_sync
-grl_media_source_query
-grl_media_source_query_sync
-grl_media_source_metadata
-grl_media_source_metadata_sync
-grl_media_source_store
-grl_media_source_store_sync
-grl_media_source_remove
-grl_media_source_remove_sync
-grl_media_source_set_auto_split_threshold
-grl_media_source_get_auto_split_threshold
-grl_media_source_test_media_from_uri
-grl_media_source_get_media_from_uri
-grl_media_source_get_media_from_uri_sync
-grl_media_source_notify_change_start
-grl_media_source_notify_change_stop
-grl_media_source_notify_change_list
-grl_media_source_notify_change
-<SUBSECTION Standard>
-GRL_MEDIA_SOURCE
-GRL_IS_MEDIA_SOURCE
-GRL_TYPE_MEDIA_SOURCE
-grl_media_source_get_type
-GRL_MEDIA_SOURCE_CLASS
-GRL_IS_MEDIA_SOURCE_CLASS
-GRL_MEDIA_SOURCE_GET_CLASS
-<SUBSECTION Private>
-GrlMediaSourcePrivate
-</SECTION>
-
-<SECTION>
 <FILE>grl-caps</FILE>
 <TITLE>GrlCaps</TITLE>
 GrlCaps
@@ -206,9 +166,7 @@ grl_operation_options_set_type_filter
 GRL_PLUGIN_PATH_VAR
 GRL_PLUGIN_RANKS_VAR
 GRL_PLUGIN_REGISTER
-GrlPluginInfo
 GrlPluginDescriptor
-GrlPluginRank
 GrlPluginRegistry
 GrlPluginRegistryClass
 grl_plugin_registry_get_default
@@ -352,8 +310,9 @@ grl_media_set_thumbnail
 grl_media_set_thumbnail_binary
 grl_media_set_site
 grl_media_set_duration
-grl_media_set_date
+grl_media_set_publication_date
 grl_media_set_creation_date
+grl_media_set_modification_date
 grl_media_set_mime
 grl_media_set_play_count
 grl_media_set_last_played
@@ -386,8 +345,9 @@ grl_media_get_thumbnail_binary
 grl_media_get_thumbnail_binary_nth
 grl_media_get_site
 grl_media_get_duration
-grl_media_get_date
+grl_media_get_publication_date
 grl_media_get_creation_date
+grl_media_get_modification_date
 grl_media_get_mime
 grl_media_get_rating
 grl_media_get_play_count
@@ -595,42 +555,43 @@ GRL_METADATA_KEY_ALBUM
 GRL_METADATA_KEY_ARTIST
 GRL_METADATA_KEY_AUTHOR
 GRL_METADATA_KEY_BITRATE
+GRL_METADATA_KEY_CAMERA_MODEL
 GRL_METADATA_KEY_CERTIFICATE
 GRL_METADATA_KEY_CHILDCOUNT
-GRL_METADATA_KEY_DATE
+GRL_METADATA_KEY_CREATION_DATE
 GRL_METADATA_KEY_DESCRIPTION
 GRL_METADATA_KEY_DURATION
+GRL_METADATA_KEY_EPISODE
+GRL_METADATA_KEY_EXPOSURE_TIME
 GRL_METADATA_KEY_EXTERNAL_PLAYER
 GRL_METADATA_KEY_EXTERNAL_URL
+GRL_METADATA_KEY_FLASH_USED
 GRL_METADATA_KEY_FRAMERATE
 GRL_METADATA_KEY_GENRE
 GRL_METADATA_KEY_HEIGHT
 GRL_METADATA_KEY_ID
+GRL_METADATA_KEY_ISO_SPEED
 GRL_METADATA_KEY_LAST_PLAYED
 GRL_METADATA_KEY_LAST_POSITION
 GRL_METADATA_KEY_LICENSE
 GRL_METADATA_KEY_LYRICS
 GRL_METADATA_KEY_MIME
+GRL_METADATA_KEY_MODIFICATION_DATE
+GRL_METADATA_KEY_ORIENTATION
 GRL_METADATA_KEY_PLAY_COUNT
+GRL_METADATA_KEY_PUBLICATION_DATE
 GRL_METADATA_KEY_RATING
+GRL_METADATA_KEY_SEASON
+GRL_METADATA_KEY_SHOW
 GRL_METADATA_KEY_SITE
 GRL_METADATA_KEY_SOURCE
+GRL_METADATA_KEY_START_TIME
 GRL_METADATA_KEY_STUDIO
 GRL_METADATA_KEY_THUMBNAIL
 GRL_METADATA_KEY_THUMBNAIL_BINARY
 GRL_METADATA_KEY_TITLE
 GRL_METADATA_KEY_URL
 GRL_METADATA_KEY_WIDTH
-GRL_METADATA_KEY_SEASON
-GRL_METADATA_KEY_EPISODE
-GRL_METADATA_KEY_SHOW
-GRL_METADATA_KEY_CREATION_DATE
-GRL_METADATA_KEY_CAMERA_MODEL
-GRL_METADATA_KEY_ORIENTATION
-GRL_METADATA_KEY_FLASH_USED
-GRL_METADATA_KEY_EXPOSURE_TIME
-GRL_METADATA_KEY_ISO_SPEED
-GRL_METADATA_KEY_START_TIME
 grl_metadata_key_get_name
 grl_metadata_key_get_desc
 grl_metadata_key_get_type
diff --git a/doc/grilo/grilo.types b/doc/grilo/grilo.types
index 0831015..3bf22ac 100644
--- a/doc/grilo/grilo.types
+++ b/doc/grilo/grilo.types
@@ -18,8 +18,6 @@ grl_media_video_get_type
 grl_media_image_get_type
 grl_plugin_get_type
 grl_source_get_type
-grl_media_source_get_type
-grl_metadata_source_get_type
 grl_plugin_registry_get_type
 grl_caps_get_type
 grl_operation_options_get_type
diff --git a/doc/grilo/plugins-sources.xml b/doc/grilo/plugins-sources.xml
new file mode 100644
index 0000000..109bae6
--- /dev/null
+++ b/doc/grilo/plugins-sources.xml
@@ -0,0 +1,1433 @@
+<?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="source-plugins-intro">
+    <title>Introduction</title>
+
+    <para>
+      Sources provide access to media content. Examples of them are sources
+      providing content from Jamendo or UPnP. Sources can also provide other
+      information that complements already existent media content. Thus, there
+      can be sources providing content and others adding more informationover
+      that content.
+    </para>
+
+    <para>
+      Sources are provided by plugins. A plugin usually provides one source, but
+      it can provide more than one. For instance, the UPnP plugin is able to
+      provide several sources, one source per each UPnP server found in the
+      network.
+    </para>
+
+    <para>
+      Usually, clients interact with these sources in various ways:
+      <itemizedlist>
+        <listitem>
+          <emphasis>Search.</emphasis>
+          Users can instruct the source 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>Resolve.</emphasis>
+          Users can request additional information (metadata)
+          for a specific media item served by a source through
+          a previous browse, search or query operation that was configured
+          to retrieve only partial metadata (typically for optimization
+          purposes). Resolve operations are usually used when showing
+          detailed information about specific media items.
+        </listitem>
+        <listitem>
+          <emphasis>Store.</emphasis>
+          Some sources 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. A source can store either the
+          full media or just a subset of their properties.
+        </listitem>
+        <listitem>
+          <emphasis>Remove.</emphasis>
+          The opposite to the Store operation, used to remove
+          content from the source.
+        </listitem>
+        <listitem>
+          <emphasis>Media from URL.</emphasis>
+          This allows to build a media just knowing the URL.
+          For instance, giving a YouTube URL, the proper source
+          is able to build and return the corresponding Grilo
+          media content.
+        </listitem>
+      </itemizedlist>
+    </para>
+  </section>
+
+
+  <section id="source-plugins-basics">
+    <title>Registering 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 <link
+      linkend="GrlSource">GrlSource</link> instance and register it using <link
+      linkend="grl-plugin-registry-register-source">grl_plugin_registry_register_source()</link>.
+    </para>
+
+    <para>
+      A <link linkend="GrlSource">GrlSource</link> instance represents a
+      particular source of media/attributes. Usually each plugin would spawn
+      just one media source, but some plugins may spawn multiple 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 sources and then use the <link
+      linkend="GrlSource">GrlSource</link> 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 <link
+      linkend="GrlConfig">GrlConfig</link> objects. Usually there would be only
+      one <link linkend="GrlConfig">GrlConfig</link> 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,
+                     GrlPlugin *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_SOURCE (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 source code, for that sources must
+      extend the <link linkend="GrlSource">GrlSource</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 <link linkend="GrlSource">GrlSource</link> 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. Almost all
+      operations are optional, but for typically Search or Browse are expected in
+      sources providing media content, and Resolve for sources providing
+      information for existent content.
+    </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);
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+  source_class->supported_keys = grl_foo_source_supported_keys;
+  source_class->slow_keys = grl_foo_source_slow_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->resolve = grl_foo_source_resolve;
+}
+
+/* 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_SOURCE);
+      ]]>
+    </programlisting>
+  </section>
+
+  <section id="source-plugins-supported-keys">
+    <title>Implementing Supported Keys</title>
+
+    <para>
+      Sources should implement "supported_keys" method to define what metadata
+      keys the source is able to handle.
+    </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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->supported_keys = grl_foo_source_supported_keys;
+}
+
+static const GList *
+grl_foo_source_supported_keys (GrlSource *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="source-plugins-slow-keys">
+    <title>Implementing Slow Keys</title>
+
+    <para>
+      This method is similar to "supported_keys", and in fact it returns a
+      subset of the keys returned by "supported_keys".
+    </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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->slow_keys = grl_foo_source_slow_keys;
+}
+
+static const GList *
+grl_foo_source_slow_keys (GrlSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL,
+                                      NULL);
+  }
+  return keys;
+}
+      ]]>
+    </programlisting>
+  </section>
+
+
+  <section id="source-plugins-search">
+    <title>Implementing Search</title>
+
+    <para>
+      This method implements free-text based searches, retrieving media
+    that matches the text provided by the user.
+    </para>
+
+    <para>
+      Typically, the way this method operates is like this:
+      <itemizedlist>
+        <listitem>Plugin receives the text to search, as well as other
+        parameters like the metadata keys to retrieve, how many elements, and so
+        on.</listitem>
+        <listitem>With all this information the plugin executes the search on
+        the backend, and waits for the results.</listitem>
+        <listitem>Plugin receives the results from the service provider, and
+        encodes them in different <link linkend="GrlMedia">GrlMedia</link>
+        objects.</listitem>
+        <listitem>Plugin sends eatch <link linkend="GrlMedia">GrlMedia</link>
+        object back to the client, one by one, 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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->search = grl_foo_source_search;
+}
+
+static void
+foo_execute_search_async_cb (gchar *xml, GrlSourceSearchSpec *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 (GrlSource *source, GrlSourceSearchSpec *ss)
+{
+  gchar *foo_http_search:
+
+  foo_http_search =
+    g_strdup_printf("http://media.foo.com?text=%s&offset=%d&count=%d";,
+                    ss->text,
+                    grl_operation_options_get_skip (ss->options),
+                    grl_operation_options_get_count (ss->options));
+
+  /* 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="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="source-plugins-browse">
+    <title>Implementing Browse</title>
+
+    <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 <link
+      linkend="GrlMedia">GrlMedia</link> 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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->browse = grl_foo_source_browse;
+}
+
+static void
+foo_execute_categories_async_cb (gchar *xml, GrlSourceBrowseSpec *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, GrlSourceBrowseSpec *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 (GrlSource *source, GrlSourceBrowseSpec *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";,
+                      grl_operation_options_get_skip (bs),
+                      grl_operation_options_get_count (bs));
+     /* This executes an async http query and then invokes
+        foo_execute_categories_async_cb with the response */
+     foo_execute_categories_async (foo_http_browse, bs);
+  } else {
+    /* Browsing a specific category */
+    foo_http_browse =
+      g_strdup_printf("http://media.foo.com/content/%s?offset=%d&count=%d";,
+                      box_id,
+                      grl_operation_options_get_skip (bs),
+                      grl_operation_options_get_count (bs));
+     /* This executes an async http query and then invokes
+        foo_execute_browse_async_cb with the response */
+     foo_execute_media_async (foo_http_browse, bs);
+  }
+}
+      ]]>
+    </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 <link linkend="GrlMediaBox">GrlMediaBox</link> 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 <link
+          linkend="GrlMediaBox">GrlMediaBox</link> objects, leading to more
+          complex hierarchies.
+        </listitem>
+        <listitem>
+          <link linkend="GrlMediaBox">GrlMediaBox</link> 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 <link
+          linkend="GrlMediaBox">GrlMediaBox</link>. The plugin developer must
+          map that to something the media provider understands. Typically, when
+          <link linkend="GrlMedia">GrlMedia</link> objects are returned from a
+          plugin to the client, they are created so their "id" property (<link
+          linkend="grl-media-set-id">grl_media_set_id()</link>) can be used for
+          this purpose, identifying these media resources uniquely in the
+          context of the media provider.
+        </listitem>
+        <listitem>
+          A <link linkend="GrlMediaBox">GrlMediaBox</link> 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="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="source-plugins-query">
+    <title>Implementing Query</title>
+
+    <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. Plugin
+      documentation should explain what is the syntax of this query text, and
+      what it allows.
+    </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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->query = grl_foo_source_query;
+}
+
+static void
+grl_foo_source_query (GrlSource *source, GrlSourceQuerySpec *qs)
+{
+  const gchar *sql_filter;
+  GList *results;
+  GrlMedia *media;
+  gint count;
+
+  /* In this example, we are assuming qs->query is expected to contain a
+  suitable SQL filter */
+  sql_query = prepare_sql_with_custom_filter (qs->query,
+                                              grl_operation_options_get_skip (qs->options),
+                                              grl_operation_options_get_skip (qs->options));
+  /* 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="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="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 <link linkend="GrlMedia">GrlMedia</link> instances is
+          easy, depending on the type of media you should instantiate one of the
+          <link linkend="GrlMedia">GrlMedia</link> subclasses (<link
+          linkend="GrlMediaImage">GrlMediaImage</link>, <link
+          linkend="GrlMediaVideo">GrlMediaVideo</link>, <link
+          linkend="GrlMediaAudio">GrlMediaAudio</link> or <link
+          linkend="GrlMediaBox">GrlMediaBox</link>), 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 <link linkend="GrlMedia">GrlMedia</link> 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="source-plugins-resolve">
+    <title>Implementing Resolve</title>
+
+    <para>
+      Resolve operations are issued in order to grab additional information on a
+      given media (<link linkend="GrlMedia">GrlMedia</link>).
+    </para>
+
+    <para>
+      Typically, the use case for Resolve operations is applications obtaining a
+      list of <link linkend="GrlMedia">GrlMedia</link> 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>
+
+    <para>
+      This additional information can be provided by the same source that got
+      the <link linkend="GrlMedia">GrlMedia</link> objects (if it implements the
+      Resolve operation), or by other sources able to provide the information
+      requested.
+    </para>
+
+    <para>
+      Plugins implementing "resolve" operation should implement "may_resolve"
+      too. The purpose of this method is to analyze if the <link
+      linkend="GrlMedia">GrlMedia</link> contains the required metadata for the
+      source to provide the additional metadata requested. If not provided, the
+      default behaviour for sources implementing "resolve" but not "may_resolve"
+      is to resolve only supported keys in media objects coming from the source
+      itself.
+    </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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->may_resolve = grl_foo_source_may_resolve;
+  source_class->resolve = grl_foo_source_resolve;
+}
+
+static gboolean
+grl_foo_source_may_resolve (GrlSource *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 (GrlSource *source,
+                        GrlSourceResolveSpec *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->operation_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>
+          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 Resolve are grl-youtube, grl-upnp or
+      grl-lastfm-albumart among others.
+    </para>
+  </section>
+
+
+  <section id="source-plugins-store">
+    <title>Implementing Store</title>
+
+    <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 *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->store = grl_foo_source_store;
+}
+
+static void
+grl_foo_source_store (GrlSource *source,
+                      GrlSourceStoreSpec *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), and all the keys were stored successfully (no list of failed keys) */
+  ss->callback (ss->source, ss->media, NULL, 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="source-plugins-store-metadata">
+    <title>Implementing Store Metadata</title>
+
+    <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>store_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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->writable_keys = grl_foo_source_writable_keys;
+  source_class->store_metadata = grl_foo_source_store_metadata;
+}
+
+static const GList *
+grl_foo_source_writable_keys (GrlSource *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_store_metadata (GrlSource *source,
+                               GrlSourceSetMetadataSpec *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 "store_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 "store_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 "store_metadata" are grl-metadata-store
+      or grl-tracker.
+    </para>
+  </section>
+
+
+  <section id="source-plugins-remove">
+    <title>Implementing Remove</title>
+
+    <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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->remove = grl_foo_source_remove;
+}
+
+static void
+grl_foo_source_remove (GrlSource *source,
+                       GrlSourceRemoveSpec *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="source-plugins-media-from-uri">
+    <title>Implementing Media from URI</title>
+
+    <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 <link
+      linkend="GrlMedia">GrlMedia</link> object representing it.
+    </para>
+
+    <para>
+      Plugins that want to support URI to <link linkend="GrlMedia">GrlMedia</link>
+      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 <link
+      linkend="GrlMedia">GrlMedia</link> 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 <link linkend="GrlMedia">GrlMedia</link> 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 <link linkend="GrlMedia">GrlMedia</link> object.
+    </para>
+
+    <programlisting role="C">
+      <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->test_media_from_uri = grl_foo_source_test_media_from_uri;
+  source_class->media_from_uri = grl_foo_source_media_from_uri;
+}
+
+static gboolean
+grl_filesystem_test_media_from_uri (GrlSource *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 (GrlSource *source,
+                               GrlSourceMediaFromUriSpec *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>
+
+    <para>
+      Examples of plugins implementing "media_from_uri" are grl-filesystem
+      or grl-youtube.
+    </para>
+  </section>
+
+
+  <section id="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)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+  source_class->notify_change_start = grl_foo_source_notify_change_start;
+  source_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_source_notify_change_list (source,
+                                 changed_medias,
+                                 GRL_CONTENT_CHANGED,
+                                 FALSE);
+}
+
+static gboolean
+grl_foo_source_notify_change_start (GrlSource *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 (GrlSource *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 <link
+      linkend="grl-media-source-notify-change-list">grl_media_source_notify_change_list()</link>
+      should be used.
+    </para>
+
+    <para>
+      Examples of plugins implementing change notification are grl-upnp and
+      grl-tracker among others
+    </para>
+  </section>
+
+  <section id="source-plugins-cancel">
+    <title>Cancelling ongoing operations</title>
+
+    <para>
+      Implementing the "cancel" method is optional, as others. 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><link
+        linkend="grl-operation-set-data">grl_operation_set_data()</link></listitem>
+        <listitem><link
+        linkend="grl-operation-get-data">grl_operation_get_data()</link></listitem>
+      </itemizedlist>
+      See the API reference documentation for <link
+      linkend="grilo-grl-operation">grl-operation</link> for more details.
+    </para>
+
+    <programlisting role="C">
+      <![CDATA[
+static void
+grl_foo_source_class_init (GrlFooSourceClass * klass)
+{
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+  source_class->search = grl_foo_source_search;
+  source_class->cancel = grl_foo_source_cancel;
+}
+
+static void
+grl_foo_source_search (GrlSource *source,
+                       GrlSourceSearchSpec *ss)
+{
+  ...
+  gint op_handler = foo_service_search_start (ss->text, ...);
+  grl_operation_set_data (ss->operation_id,
+                          GINT_TO_POINTER (op_handler));
+  ...
+}
+
+static void
+grl_foo_source_cancel (GrlSource *source,
+                       guint operation_id)
+{
+  gint op_handler;
+
+  op_handler =
+    GPOINTER_TO_INT (grl_operation_get_data (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>
+
+    <para>
+      Developers must free any data stored before the operation finishes.
+    </para>
+  </section>
+
+</section>
diff --git a/libs/net/grl-net-wc.h b/libs/net/grl-net-wc.h
index 58509bd..05db8fa 100644
--- a/libs/net/grl-net-wc.h
+++ b/libs/net/grl-net-wc.h
@@ -96,12 +96,11 @@ struct _GrlNetWc
 /**
  * GrlNetWcClass:
  * @parent_class: the parent class structure
- * @cache: class variable for cache definition
- * @cache_size: class variable for the cache size value
  *
  * Grilo web client helper class.
  *
- * It's a simple and thin web client for be used by the sources.
+ * It's a simple and thin web client to be used by the sources to download
+ * content from Internet.
  */
 struct _GrlNetWcClass
 {
diff --git a/src/grl-caps.c b/src/grl-caps.c
index 2a9d6cb..8c1f660 100644
--- a/src/grl-caps.c
+++ b/src/grl-caps.c
@@ -182,6 +182,7 @@ grl_caps_set_type_filter (GrlCaps *caps, GrlTypeFilter filter)
 
 /**
  * grl_caps_get_key_filter:
+ * @caps: a #GrlCaps instance
  *
  * Returns: (transfer none) (element-type GrlKeyID):
  */
@@ -195,7 +196,7 @@ grl_caps_get_key_filter (GrlCaps *caps)
 
 /**
  * grl_caps_set_key_filter:
- * @caps:
+ * @caps: a #GrlCaps instance
  * @keys: (transfer none) (element-type GrlKeyID):
  */
 void
@@ -225,7 +226,7 @@ grl_caps_is_key_filter (GrlCaps *caps, GrlKeyID key)
 
 /**
  * grl_caps_get_key_range_filter:
- * @caps:
+ * @caps: a #GrlCaps instance
  *
  * Returns: (transfer none) (element-type GrlKeyID):
  */
@@ -239,7 +240,7 @@ grl_caps_get_key_range_filter (GrlCaps *caps)
 
 /**
  * grl_caps_set_key_range_filter:
- * @caps:
+ * @caps: a #GrlCaps instance
  * @keys: (transfer none) (element-type GrlKeyID):
  */
 void
diff --git a/src/grl-multiple.c b/src/grl-multiple.c
index e7cf706..abd8853 100644
--- a/src/grl-multiple.c
+++ b/src/grl-multiple.c
@@ -23,7 +23,7 @@
 /**
  * SECTION:grl-multiple
  * @short_description: Search in multiple loaded sources
- * @see_also: #GrlMediaPlugin, #GrlMetadataSource, #GrlMediaSource
+ * @see_also: #GrlPlugin, #GrlSource
  *
  * These helper functions are due to ease the search in multiple sources.
  * You can specify the list of sources to use for the searching. Those sources
diff --git a/src/grl-operation-options.c b/src/grl-operation-options.c
index b440f82..f5fd424 100644
--- a/src/grl-operation-options.c
+++ b/src/grl-operation-options.c
@@ -580,10 +580,9 @@ grl_operation_options_set_key_filters (GrlOperationOptions *options,
 
 /**
  * grl_operation_options_set_key_filter_dictionary:
- * @options:
+ * @options: a #GrlOperationOptions instance
  * @filters: (transfer none) (element-type GrlKeyID GValue):
  *
- *
  * Rename to: grl_operation_options_set_key_filters
  */
 gboolean
@@ -607,7 +606,7 @@ grl_operation_options_set_key_filter_dictionary (GrlOperationOptions *options,
 
 /**
  * grl_operation_options_get_key_filter:
- * @options:
+ * @options: a #GrlOperationOptions instance
  * @key:
  *
  * Returns: (transfer none): the filter
@@ -622,6 +621,7 @@ grl_operation_options_get_key_filter (GrlOperationOptions *options,
 
 /**
  * grl_operation_options_get_key_filter_list:
+ * @options: a #GrlOperationOptions instance
  *
  * Returns: (transfer container) (element-type GrlKeyID):
  */
@@ -757,7 +757,7 @@ grl_operation_options_get_key_range_filter (GrlOperationOptions *options,
 
 /**
  * grl_operation_options_get_key_range_filter_list:
- * @options:
+ * @options: a #GrlOperationOptions instance
  *
  * Returns: (transfer container) (element-type GrlKeyID):
  */
diff --git a/src/grl-plugin-registry.c b/src/grl-plugin-registry.c
index 0f4c1f9..aff59b9 100644
--- a/src/grl-plugin-registry.c
+++ b/src/grl-plugin-registry.c
@@ -24,7 +24,7 @@
 /**
  * SECTION:grl-plugin-registry
  * @short_description: Grilo plugins loader and manager
- * @see_also: #GrlPlugin, #GrlSource, #GrlMetadataSource, #GrlMediaSource
+ * @see_also: #GrlPlugin, #GrlSource
  *
  * The registry holds the metadata of a set of plugins.
  *
@@ -33,9 +33,8 @@
  * on disk, and may or may not be loaded a given time. There only can be
  * a single instance of #GrlPluginRegistry (singleton pattern).
  *
- * A #GrlPlugin can hold several data sources (#GrlMetadataSource or
- * #GrlMediaSource), and #GrlPluginRegistry shall register each one of
- * them.
+ * A #GrlPlugin can hold several data #GrlSource sources, and #GrlPluginRegistry
+ * shall register each one of them.
  */
 
 #include "grl-plugin-registry.h"
diff --git a/src/grl-source.c b/src/grl-source.c
index 2f2cca0..a5b3972 100644
--- a/src/grl-source.c
+++ b/src/grl-source.c
@@ -2935,7 +2935,7 @@ grl_source_supported_operations (GrlSource *source)
  *
  * Gets how much elements the source is able to handle in a single request.
  *
- * See #grilo_source_set_auto_split_threshold()
+ * See #grl_source_set_auto_split_threshold()
  *
  * Returns: the assigned threshold, or 0 if there is no threshold
  */
@@ -4028,6 +4028,7 @@ grl_source_store (GrlSource *source,
  * @source: a source
  * @parent: (allow-none): a #GrlMediaBox to store the data transfer objects
  * @media: a #GrlMedia data transfer object
+ * @flags: flags to configure specific behaviour of the operation
  * @error: a #GError, or @NULL
  *
  * Store the @media into the @parent container.
diff --git a/src/grl-source.h b/src/grl-source.h
index c73c21b..4f8e44c 100644
--- a/src/grl-source.h
+++ b/src/grl-source.h
@@ -299,7 +299,7 @@ typedef struct {
 /**
  * GrlSourceQuerySpec:
  * @source: a source
- * @query_id: operation identifier
+ * @operation_id: operation identifier
  * @query: the query to process
  * @keys: the #GList of #GrlKeyID<!-- -->s to request
  * @options: options wanted for that operation
@@ -408,9 +408,12 @@ typedef struct _GrlSourceClass GrlSourceClass;
  * @may_resolve: return FALSE if it can be known without blocking that @key_id
  * @test_media_from_uri: tests if this source can create #GrlMedia
  * instances from a given URI.
+ * @media_from_uri: Creates a #GrlMedia instance representing the media
+ * exposed by a certain URI.
  * @browse: browse through a list of media
  * @search: search for media
  * @query: query for a specific media
+ * @remove: remove a media from a container
  * @store: store a media in a container
  * @store_metadata: update metadata values for a given object in a
  * permanent fashion



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