[grilo] core: Merge GrlMediaSource and GrlMetadataSource into GrlSource



commit b9b02d0cff289c8e88f3555cceda8154fd295bf3
Author: Juan A. Suarez Romero <jasuarez igalia com>
Date:   Fri Jun 3 08:32:35 2011 +0200

    core: Merge GrlMediaSource and GrlMetadataSource into GrlSource
    
    It merges both types of sources in just one, improving also the full
    resolution algorithm.

 bindings/vala/grilo-0.2-custom.vala      |   64 +-
 bindings/vala/grilo-0.2.metadata         |   50 +-
 bindings/vala/grilo-uninstalled.files.in |    3 -
 doc/grilo/Makefile.am                    |    1 -
 examples/browsing.c                      |   14 +-
 examples/efficient-metadata-resolution.c |   22 +-
 examples/multivalues.c                   |    4 +-
 examples/searching.c                     |    4 +-
 src/Makefile.am                          |   15 +-
 src/grilo.h                              |    3 +-
 src/grl-error.h                          |    6 +-
 src/grl-log-priv.h                       |    2 -
 src/grl-log.c                            |    4 -
 src/grl-media-source.c                   | 2931 ----------------------
 src/grl-media-source.h                   |  521 ----
 src/grl-metadata-source.c                |  694 ------
 src/grl-metadata-source.h                |  252 --
 src/grl-multiple.c                       |   82 +-
 src/grl-multiple.h                       |    8 +-
 src/grl-operation-options.c              |    4 +-
 src/grl-operation-options.h              |   22 +-
 src/grl-plugin-registry.h                |    4 +-
 src/grl-plugin.c                         |    2 +-
 src/grl-source-priv.h                    |   75 -
 src/grl-source.c                         | 3964 ++++++++++++++++++++++++++++--
 src/grl-source.h                         |  480 ++++-
 tools/grilo-inspect/grl-inspect.c        |   13 +-
 tools/grilo-test-ui/main.c               |  200 +-
 tools/vala/grilo-test.vala               |   14 +-
 29 files changed, 4458 insertions(+), 5000 deletions(-)
---
diff --git a/bindings/vala/grilo-0.2-custom.vala b/bindings/vala/grilo-0.2-custom.vala
index d3e998a..fad2a0f 100644
--- a/bindings/vala/grilo-0.2-custom.vala
+++ b/bindings/vala/grilo-0.2-custom.vala
@@ -1,77 +1,77 @@
 namespace Grl {
 	[CCode (instance_pos = 3.1)]
-	public delegate void MediaSourceMetadataCb (Grl.MediaSource source, uint operation_id, Grl.Media? media, GLib.Error error);
+	public delegate void SourceResolveCb (Grl.Source source, uint operation_id, Grl.Media? media, GLib.Error error);
 	[CCode (instance_pos = 2.1)]
-	public delegate void MediaSourceRemoveCb (Grl.MediaSource source, Grl.Media? media, GLib.Error error);
+	public delegate void SourceRemoveCb (Grl.Source source, Grl.Media? media, GLib.Error error);
 	[CCode (instance_pos = 4.1)]
-	public delegate void MediaSourceResultCb (Grl.MediaSource source, uint operation_id, Grl.Media? media, uint remaining, GLib.Error? error);
+	public delegate void SourceResultCb (Grl.Source source, uint operation_id, Grl.Media? media, uint remaining, GLib.Error? error);
 	[CCode (instance_pos = 3.1)]
-	public delegate void MediaSourceStoreCb (Grl.MediaSource source, Grl.MediaBox? parent, Grl.Media? media, GLib.Error? error);
+	public delegate void SourceStoreCb (Grl.Source source, Grl.MediaBox? parent, Grl.Media? media, GLib.Error? error);
 	[CCode (instance_pos = 3.1)]
-	public delegate void MetadataSourceResolveCb (Grl.MetadataSource source, uint operation_id, Grl.Media? media, GLib.Error? error);
+	public delegate void SourceResolveCb (Grl.Source source, uint operation_id, Grl.Media? media, GLib.Error? error);
 	[CCode (instance_pos = 3.1)]
-	public delegate void MetadataSourceSetMetadataCb (Grl.MetadataSource source, Grl.Media? media, GLib.List failed_keys, GLib.Error? error);
+	public delegate void SourceStoreMetadataCb (Grl.Source source, Grl.Media? media, GLib.List failed_keys, GLib.Error? error);
 
 	[Compact]
 	public class MetadataKey {
 		[CCode (cname ="GRL_METADATA_KEY_ALBUM")]
-		public static GLib.ParamSpec ALBUM;
+		public GLib.ParamSpec ALBUM;
 		[CCode (cname ="GRL_METADATA_KEY_ARTIST")]
-		public static GLib.ParamSpec ARTIST;
+		public GLib.ParamSpec ARTIST;
 		[CCode (cname ="GRL_METADATA_KEY_AUTHOR")]
-		public static GLib.ParamSpec AUTHOR;
+		public GLib.ParamSpec AUTHOR;
 		[CCode (cname ="GRL_METADATA_KEY_BITRATE")]
-		public static GLib.ParamSpec BITRATE;
+		public GLib.ParamSpec BITRATE;
 		[CCode (cname ="GRL_METADATA_KEY_CERTIFICATE")]
-		public static GLib.ParamSpec CERTIFICATE;
+		public GLib.ParamSpec CERTIFICATE;
 		[CCode (cname ="GRL_METADATA_KEY_CHILDCOUNT")]
-		public static GLib.ParamSpec CHILDCOUNT;
+		public GLib.ParamSpec CHILDCOUNT;
 		[CCode (cname ="GRL_METADATA_KEY_DATE")]
-		public static GLib.ParamSpec DATE;
+		public GLib.ParamSpec DATE;
 		[CCode (cname ="GRL_METADATA_KEY_DESCRIPTION")]
-		public static GLib.ParamSpec DESCRIPTION;
+		public GLib.ParamSpec DESCRIPTION;
 		[CCode (cname ="GRL_METADATA_KEY_DURATION")]
-		public static GLib.ParamSpec DURATION;
+		public GLib.ParamSpec DURATION;
 		[CCode (cname ="GRL_METADATA_KEY_EXTERNAL_PLAYER")]
-		public static GLib.ParamSpec EXTERNAL_PLAYER;
+		public GLib.ParamSpec EXTERNAL_PLAYER;
 		[CCode (cname ="GRL_METADATA_KEY_EXTERNAL_URL")]
-		public static GLib.ParamSpec EXTERNAL_URL;
+		public GLib.ParamSpec EXTERNAL_URL;
 		[CCode (cname ="GRL_METADATA_KEY_FRAMERATE")]
-		public static GLib.ParamSpec FRAMERATE;
+		public GLib.ParamSpec FRAMERATE;
 		[CCode (cname ="GRL_METADATA_KEY_GENRE")]
-		public static GLib.ParamSpec GENRE;
+		public GLib.ParamSpec GENRE;
 		[CCode (cname ="GRL_METADATA_KEY_HEIGHT")]
-		public static GLib.ParamSpec HEIGHT;
+		public GLib.ParamSpec HEIGHT;
 		[CCode (cname ="GRL_METADATA_KEY_ID")]
 		public static GLib.ParamSpec ID;
 		[CCode (cname ="GRL_METADATA_KEY_LAST_PLAYED")]
-		public static GLib.ParamSpec LAST_PLAYED;
+		public GLib.ParamSpec LAST_PLAYED;
 		[CCode (cname ="GRL_METADATA_KEY_LAST_POSITION")]
-		public static GLib.ParamSpec LAST_POSITION;
+		public GLib.ParamSpec LAST_POSITION;
 		[CCode (cname ="GRL_METADATA_KEY_LICENSE")]
-		public static GLib.ParamSpec LICENSE;
+		public GLib.ParamSpec LICENSE;
 		[CCode (cname ="GRL_METADATA_KEY_LYRICS")]
-		public static GLib.ParamSpec LYRICS;
+		public GLib.ParamSpec LYRICS;
 		[CCode (cname ="GRL_METADATA_KEY_MIME")]
-		public static GLib.ParamSpec MIME;
+		public GLib.ParamSpec MIME;
 		[CCode (cname ="GRL_METADATA_KEY_PLAY_COUNT")]
-		public static GLib.ParamSpec PLAY_COUNT;
+		public GLib.ParamSpec PLAY_COUNT;
 		[CCode (cname ="GRL_METADATA_KEY_RATING")]
-		public static GLib.ParamSpec RATING;
+		public GLib.ParamSpec RATING;
 		[CCode (cname ="GRL_METADATA_KEY_SITE")]
-		public static GLib.ParamSpec SITE;
+		public GLib.ParamSpec SITE;
 		[CCode (cname ="GRL_METADATA_KEY_SOURCE")]
-		public static GLib.ParamSpec SOURCE;
+		public GLib.ParamSpec SOURCE;
 		[CCode (cname ="GRL_METADATA_KEY_STUDIO")]
-		public static GLib.ParamSpec STUDIO;
+		public GLib.ParamSpec STUDIO;
 		[CCode (cname ="GRL_METADATA_KEY_THUMBNAIL")]
-		public static GLib.ParamSpec THUMBNAIL;
+		public GLib.ParamSpec THUMBNAIL;
 		[CCode (cname ="GRL_METADATA_KEY_TITLE")]
 		public static GLib.ParamSpec TITLE;
 		[CCode (cname ="GRL_METADATA_KEY_URL")]
 		public static GLib.ParamSpec URL;
 		[CCode (cname ="GRL_METADATA_KEY_WIDTH")]
-		public static GLib.ParamSpec WIDTH;
+		public GLib.ParamSpec WIDTH;
 
 		public static unowned GLib.List list_new (GLib.ParamSpec p, ...);
 	}
diff --git a/bindings/vala/grilo-0.2.metadata b/bindings/vala/grilo-0.2.metadata
index 2c7de4f..4c59024 100644
--- a/bindings/vala/grilo-0.2.metadata
+++ b/bindings/vala/grilo-0.2.metadata
@@ -13,32 +13,25 @@ grl_plugin_registry_get_sources_by_operations type_arguments="unowned Plugin" tr
 # GrlPlugin
 grl_plugin_get_info_keys type_arguments="unowned string" transfer_ownership="1"
 
-#GrlSource
+# GrlSource
 grl_source_slow_keys type_arguments="unowned KeyID" transfer_ownership="0"
 grl_source_supported_keys type_arguments="unowned KeyID" transfer_ownership="0"
 grl_source_writable_keys type_arguments="unowned KeyID" transfer_ownership="0"
-
-# GrlMediaSource
-grl_media_source_browse.keys type_arguments="KeyID"
-grl_media_source_browse_sync type_arguments="Media" transfer_ownership="1"
-grl_media_source_browse_sync.keys type_arguments="KeyID"
-grl_media_source_metadata.keys type_arguments="KeyID"
-grl_media_source_metadata_sync transfer_ownership="1"
-grl_media_source_metadata_sync.keys type_arguments="KeyID"
-grl_media_source_query.keys type_arguments="KeyID"
-grl_media_source_query_sync type_arguments="Media" transfer_ownership="1"
-grl_media_source_query_sync.keys type_arguments="KeyID"
-grl_media_source_search.keys type_arguments="KeyID"
-grl_media_source_search_sync type_arguments="Media" transfer_ownership="1"
-grl_media_source_search_sync.keys type_arguments="KeyID"
-
-# GrlMetadataSource
-grl_metadata_source_may_resolve.missing_keys type_arguments="unowned KeyID" is_out="1" transfer_ownership="1"
-grl_metadata_source_resolve.keys type_arguments="KeyID"
-grl_metadata_source_resolve_sync.keys type_arguments="KeyID"
-grl_metadata_source_set_metadata.keys type_arguments="KeyID"
-grl_metadata_source_set_metadata_sync.keys type_arguments="KeyID"
-grl_metadata_source_key_depends type_arguments="unowned KeyID" transfer_ownership="0"
+grl_source_browse.keys type_arguments="KeyID"
+grl_source_browse_sync type_arguments="Media" transfer_ownership="1"
+grl_source_browse_sync.keys type_arguments="KeyID"
+grl_source_query.keys type_arguments="KeyID"
+grl_source_query_sync type_arguments="Media" transfer_ownership="1"
+grl_source_query_sync.keys type_arguments="KeyID"
+grl_source_search.keys type_arguments="KeyID"
+grl_source_search_sync type_arguments="Media" transfer_ownership="1"
+grl_source_search_sync.keys type_arguments="KeyID"
+grl_source_may_resolve.missing_keys type_arguments="unowned KeyID" is_out="1" transfer_ownership="1"
+grl_source_resolve.keys type_arguments="KeyID"
+grl_source_resolve_sync.keys type_arguments="KeyID"
+grl_source_store_metadata.keys type_arguments="KeyID"
+grl_source_set_metadata_sync.keys type_arguments="KeyID"
+grl_source_key_depends type_arguments="unowned KeyID" transfer_ownership="0"
 
 # GrlData
 grl_data_get_keys type_arguments="unowned KeyID" transfer_ownership="1"
@@ -47,12 +40,11 @@ grl_data_get_keys type_arguments="unowned KeyID" transfer_ownership="1"
 GrlCoreError errordomain="1"
 
 # Callbacks
-GrlMediaSourceMetadataCb hidden="1"
-GrlMediaSourceRemoveCb hidden="1"
-GrlMediaSourceResultCb hidden="1"
-GrlMediaSourceStoreCb hidden="1"
-GrlMetadataSourceResolveCb hidden="1"
-GrlMetadataSourceSetMetadataCb hidden="1"
+GrlSourceRemoveCb hidden="1"
+GrlSourceResultCb hidden="1"
+GrlSourceStoreCb hidden="1"
+GrlSourceStoreMetadataCb hidden="1"
+GrlSourceResolveCb hidden="1"
 
 # Util
 grl_list_from_va ellipsis="1"
diff --git a/bindings/vala/grilo-uninstalled.files.in b/bindings/vala/grilo-uninstalled.files.in
index e1e99f2..5ea9b65 100644
--- a/bindings/vala/grilo-uninstalled.files.in
+++ b/bindings/vala/grilo-uninstalled.files.in
@@ -3,14 +3,11 @@
 @top_builddir@/src/grl-log.h
 @top_builddir@/src/grl-plugin.h
 @top_builddir@/src/grl-source.h
- top_builddir@/src/grl-media-source.h
 @top_builddir@/src/grl-metadata-key.h
 @top_builddir@/src/grl-caps.h
 @top_builddir@/src/grl-operation-options.h
- top_builddir@/src/grl-metadata-source.h
 @top_builddir@/src/grl-multiple.h
 @top_builddir@/src/grl-plugin-registry.h
 @top_builddir@/src/grl-util.h
 @top_builddir@/src/data/
 @top_builddir@/src/.libs/libgrilo-0.2.so
-
diff --git a/doc/grilo/Makefile.am b/doc/grilo/Makefile.am
index 1e47c42..7157aa6 100644
--- a/doc/grilo/Makefile.am
+++ b/doc/grilo/Makefile.am
@@ -60,7 +60,6 @@ EXTRA_HFILES=
 # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
 IGNORE_HFILES=config.h \
 	grl-media-plugin-priv.h \
-	grl-metadata-source-priv.h \
 	grl-operations-priv.h \
 	grl-sync-priv.h \
 	grl-metadata-key-priv.h \
diff --git a/examples/browsing.c b/examples/browsing.c
index d9459da..78967e0 100644
--- a/examples/browsing.c
+++ b/examples/browsing.c
@@ -20,7 +20,7 @@ GRL_LOG_DOMAIN_STATIC(example_log_domain);
    5) User data passed to the grl_media_source_browse method.
    6) A GError if an error happened, NULL otherwise */
 static void
-browse_cb (GrlMediaSource *source,
+browse_cb (GrlSource *source,
 	   guint browse_id,
 	   GrlMedia *media,
 	   guint remaining,
@@ -96,12 +96,12 @@ source_added_cb (GrlPluginRegistry *registry, GrlSource *source, gpointer user_d
     grl_operation_options_set_count (options, 5);
     grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY);
 
-    grl_media_source_browse (GRL_MEDIA_SOURCE (source),
-			     NULL,
-			     keys,
-           options,
-			     browse_cb,
-			     NULL);
+    grl_source_browse (source,
+                       NULL,
+                       keys,
+                       options,
+                       browse_cb,
+                       NULL);
     g_object_unref (caps);
     g_object_unref (options);
   }
diff --git a/examples/efficient-metadata-resolution.c b/examples/efficient-metadata-resolution.c
index 79a6b06..a5fdc9d 100644
--- a/examples/efficient-metadata-resolution.c
+++ b/examples/efficient-metadata-resolution.c
@@ -14,14 +14,14 @@ GRL_LOG_DOMAIN_STATIC(example_log_domain);
 const gchar *target_source_id = NULL;
 
 static void
-metadata_cb (GrlMediaSource *source,
-             guint metadata_id,
-	     GrlMedia *media,
-	     gpointer user_data,
-	     const GError *error)
+resolve_cb (GrlSource *source,
+            guint operation_id,
+            GrlMedia *media,
+            gpointer user_data,
+            const GError *error)
 {
   if (error)
-    g_error ("Metadata operation failed. Reason: %s", error->message);
+    g_error ("Resolve operation failed. Reason: %s", error->message);
 
   const gchar *url = grl_media_get_url (media);
   g_debug ("\tURL: %s", url);
@@ -30,7 +30,7 @@ metadata_cb (GrlMediaSource *source,
 }
 
 static void
-search_cb (GrlMediaSource *source,
+search_cb (GrlSource *source,
 	   guint browse_id,
 	   GrlMedia *media,
 	   guint remaining,
@@ -59,14 +59,14 @@ search_cb (GrlMediaSource *source,
     GrlCaps *caps;
     GList *keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL);
 
-    caps = grl_source_get_caps (GRL_SOURCE (source), GRL_OP_METADATA);
+    caps = grl_source_get_caps (source, GRL_OP_RESOLVE);
     options = grl_operation_options_new (caps);
     grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY);
-    grl_media_source_metadata (source,
+    grl_source_resolve (source,
 			       media,
 			       keys,
 			       options,
-			       metadata_cb,
+			       resolve_cb,
 			       NULL);
     g_object_unref (caps);
     g_object_unref (options);
@@ -102,7 +102,7 @@ source_added_cb (GrlPluginRegistry *registry, GrlSource *source, gpointer user_d
 
   /* Retrieve the first media from the source matching the text "rock" */
   g_debug ("Searching \"rock\" in \"%s\"", source_id);
-  grl_media_source_search (GRL_MEDIA_SOURCE (source),
+  grl_source_search (source,
 			   "rock",
 			   keys,
 			   options,
diff --git a/examples/multivalues.c b/examples/multivalues.c
index 0043afb..429a846 100644
--- a/examples/multivalues.c
+++ b/examples/multivalues.c
@@ -12,7 +12,7 @@
 GRL_LOG_DOMAIN_STATIC(example_log_domain);
 
 static void
-search_cb (GrlMediaSource *source,
+search_cb (GrlSource *source,
 	   guint browse_id,
 	   GrlMedia *media,
 	   guint remaining,
@@ -77,7 +77,7 @@ source_added_cb (GrlPluginRegistry *registry, GrlSource *source, gpointer user_d
   grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY);
 
   g_debug ("Searching \"rock\" in Youtube");
-  grl_media_source_search (GRL_MEDIA_SOURCE (source),
+  grl_source_search (source,
 			   "rock",
 			   keys,
 			   options,
diff --git a/examples/searching.c b/examples/searching.c
index 45b4ce7..540f03e 100644
--- a/examples/searching.c
+++ b/examples/searching.c
@@ -11,7 +11,7 @@
 GRL_LOG_DOMAIN_STATIC(example_log_domain);
 
 static void
-search_cb (GrlMediaSource *source,
+search_cb (GrlSource *source,
 	   guint browse_id,
 	   GrlMedia *media,
 	   guint remaining,
@@ -73,7 +73,7 @@ source_added_cb (GrlPluginRegistry *registry, GrlSource *source, gpointer user_d
   grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY);
 
   g_debug ("Searching \"rock\" in Jamendo");
-  grl_media_source_search (GRL_MEDIA_SOURCE (source),
+  grl_source_search (source,
 			   "rock",
 			   keys,
 			   options,
diff --git a/src/Makefile.am b/src/Makefile.am
index 4a6bc7e..0b3b636 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,7 +15,7 @@ grl-marshal.c: grl-marshal.h grl-marshal.list
 	$(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix grl_marshal	\
 	--body grl-marshal.list >> $@
 
-enum_headers = grl-media-source.h grl-caps.h
+enum_headers = grl-source.h grl-caps.h grl-operation-options.h
 
 grl-type-builtins.h: $(enum_headers) grl-type-builtins.h.template
 	$(AM_V_GEN) $(GLIB_MKENUMS) --template grl-type-builtins.h.template	\
@@ -43,16 +43,14 @@ lib GRL_NAME@_la_LDFLAGS =	\
 lib GRL_NAME@_la_SOURCES =					\
 	grl-plugin.c grl-plugin-priv.h		\
 	grl-plugin-registry.c grl-plugin-registry-priv.h	\
-	grl-source.c grl-source-priv.h			\
 	grl-metadata-key.c grl-metadata-key-priv.h		\
-	grl-metadata-source.c	\
-	grl-operation.c grl-operation.h				\
 	grl-type-builtins.c grl-type-builtins.h			\
 	grl-marshal.c grl-marshal.h				\
-	grl-media-source.c grl-util.c				\
-	grl-multiple.c						\
+	grl-operation.c grl-operation.h				\
+	grl-operation-priv.h grl-sync.c						\
+	grl-source.c			\
+	grl-util.c grl-multiple.c						\
 	grl-log.c grl-log-priv.h				\
-	grl-sync.c						\
 	grl-value-helper.c					\
 	grl-caps.c 						\
 	grl-operation-options.c grl-operation-options-priv.h	\
@@ -81,8 +79,6 @@ lib GRL_NAME@inc_HEADERS =	\
 	grl-plugin-registry.h	\
 	grl-metadata-key.h	\
 	grl-source.h	\
-	grl-metadata-source.h	\
-	grl-media-source.h	\
 	grl-log.h 		\
 	grl-multiple.h		\
 	grl-util.h		\
@@ -108,7 +104,6 @@ lib GRL_NAME@inc_HEADERS += $(data_h_headers)
 noinst_HEADERS =			\
 	grl-plugin-registry-priv.h	\
 	grl-plugin-priv.h		\
-	grl-source-priv.h		\
 	grl-metadata-key-priv.h		\
 	grl-operation-priv.h		\
 	grl-sync-priv.h			\
diff --git a/src/grilo.h b/src/grilo.h
index b424571..dce9115 100644
--- a/src/grilo.h
+++ b/src/grilo.h
@@ -31,8 +31,6 @@
 #include <grl-log.h>
 #include <grl-plugin-registry.h>
 #include <grl-plugin.h>
-#include <grl-media-source.h>
-#include <grl-metadata-source.h>
 #include <grl-metadata-key.h>
 #include <grl-data.h>
 #include <grl-media.h>
@@ -42,6 +40,7 @@
 #include <grl-media-box.h>
 #include <grl-config.h>
 #include <grl-related-keys.h>
+#include <grl-source.h>
 #include <grl-multiple.h>
 #include <grl-util.h>
 #include <grl-definitions.h>
diff --git a/src/grl-error.h b/src/grl-error.h
index 256aeed..cddf0af 100644
--- a/src/grl-error.h
+++ b/src/grl-error.h
@@ -42,12 +42,11 @@
  * @GRL_CORE_ERROR_SEARCH_FAILED: The search operation failed
  * @GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED: Searching NULL-text is not supported
  * @GRL_CORE_ERROR_QUERY_FAILED: The query operation failed
- * @GRL_CORE_ERROR_METADATA_FAILED: The metadata search failed
  * @GRL_CORE_ERROR_RESOLVE_FAILED: The resolution operation failed
  * @GRL_CORE_ERROR_MEDIA_NOT_FOUND: The media was not found
  * @GRL_CORE_ERROR_STORE_FAILED: The store operation failed
+ * @GRL_CORE_ERROR_STORE_METADATA_FAILED: The store metadata operation failed
  * @GRL_CORE_ERROR_REMOVE_FAILED: The removal operation failed
- * @GRL_CORE_ERROR_SET_METADATA_FAILED: The set metadata operation failed
  * @GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED: The media from_uri operation failed
  * @GRL_CORE_ERROR_CONFIG_LOAD_FAILED: Failed to load plugin configuration from a file
  * @GRL_CORE_ERROR_CONFIG_FAILED: Failed to set configuration for plugin
@@ -65,12 +64,11 @@ typedef enum {
   GRL_CORE_ERROR_SEARCH_FAILED,
   GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED,
   GRL_CORE_ERROR_QUERY_FAILED,
-  GRL_CORE_ERROR_METADATA_FAILED,
   GRL_CORE_ERROR_RESOLVE_FAILED,
   GRL_CORE_ERROR_MEDIA_NOT_FOUND,
   GRL_CORE_ERROR_STORE_FAILED,
+  GRL_CORE_ERROR_STORE_METADATA_FAILED,
   GRL_CORE_ERROR_REMOVE_FAILED,
-  GRL_CORE_ERROR_SET_METADATA_FAILED,
   GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
   GRL_CORE_ERROR_CONFIG_LOAD_FAILED,
   GRL_CORE_ERROR_CONFIG_FAILED,
diff --git a/src/grl-log-priv.h b/src/grl-log-priv.h
index d6049a2..d0e6c6d 100644
--- a/src/grl-log-priv.h
+++ b/src/grl-log-priv.h
@@ -35,8 +35,6 @@ GRL_LOG_DOMAIN_EXTERN(data_log_domain);
 GRL_LOG_DOMAIN_EXTERN(media_log_domain);
 GRL_LOG_DOMAIN_EXTERN(plugin_log_domain);
 GRL_LOG_DOMAIN_EXTERN(source_log_domain);
-GRL_LOG_DOMAIN_EXTERN(media_source_log_domain);
-GRL_LOG_DOMAIN_EXTERN(metadata_source_log_domain);
 GRL_LOG_DOMAIN_EXTERN(multiple_log_domain);
 GRL_LOG_DOMAIN_EXTERN(plugin_registry_log_domain);
 
diff --git a/src/grl-log.c b/src/grl-log.c
index 45127af..0cbc7ca 100644
--- a/src/grl-log.c
+++ b/src/grl-log.c
@@ -328,8 +328,6 @@ _grl_log_init_core_domains (void)
   DOMAIN_INIT (media_log_domain, "media");
   DOMAIN_INIT (plugin_log_domain, "plugin");
   DOMAIN_INIT (source_log_domain, "source");
-  DOMAIN_INIT (media_source_log_domain, "media-source");
-  DOMAIN_INIT (metadata_source_log_domain, "metadata-source");
   DOMAIN_INIT (multiple_log_domain, "multiple");
   DOMAIN_INIT (plugin_registry_log_domain, "plugin-registry");
 
@@ -373,9 +371,7 @@ _grl_log_free_core_domains (void)
   DOMAIN_FREE (config_log_domain);
   DOMAIN_FREE (media_log_domain);
   DOMAIN_FREE (plugin_log_domain);
-  DOMAIN_FREE (media_source_log_domain);
   DOMAIN_FREE (source_log_domain);
-  DOMAIN_FREE (metadata_source_log_domain);
   DOMAIN_FREE (multiple_log_domain);
   DOMAIN_FREE (plugin_registry_log_domain);
 
diff --git a/src/grl-multiple.c b/src/grl-multiple.c
index 32ca126..e7cf706 100644
--- a/src/grl-multiple.c
+++ b/src/grl-multiple.c
@@ -58,7 +58,7 @@ struct MultipleSearchData {
   GList *sources_more;
   gchar *text;
   GrlOperationOptions *options;
-  GrlMediaSourceResultCb user_callback;
+  GrlSourceResultCb user_callback;
   gpointer user_data;
 };
 
@@ -70,26 +70,24 @@ struct ResultCount {
 };
 
 struct CallbackData {
-  GrlMediaSourceResultCb user_callback;
+  GrlSourceResultCb user_callback;
   gpointer user_data;
 };
 
 struct MediaFromUriCallbackData {
   gchar *uri;
-  GrlMediaSourceMetadataCb user_callback;
+  GrlSourceResolveCb user_callback;
   gpointer user_data;
 };
 
-static void multiple_search_cb (GrlMediaSource *source,
-				guint search_id,
-				GrlMedia *media,
-				guint remaining,
-				gpointer user_data,
-				const GError *error);
-static void multiple_search_cancel_cb (struct MultipleSearchData *msd);
-
+static void multiple_search_cb (GrlSource *source,
+                                guint search_id,
+                                GrlMedia *media,
+                                guint remaining,
+                                gpointer user_data,
+                                const GError *error);
 
-/* ================= Globals ================= */
+static void multiple_search_cancel_cb (struct MultipleSearchData *msd);
 
 /* ================ Utitilies ================ */
 
@@ -132,7 +130,7 @@ handle_no_searchable_sources_idle (gpointer user_data)
 }
 
 static void
-handle_no_searchable_sources (GrlMediaSourceResultCb callback, gpointer user_data)
+handle_no_searchable_sources (GrlSourceResultCb callback, gpointer user_data)
 {
   struct CallbackData *callback_data = g_new0 (struct CallbackData, 1);
   callback_data->user_callback = callback;
@@ -148,7 +146,7 @@ start_multiple_search_operation (guint search_id,
 				 const GList *skip_counts,
 				 gint count,
 				 GrlOperationOptions *options,
-				 GrlMediaSourceResultCb user_callback,
+				 GrlSourceResultCb user_callback,
 				 gpointer user_data)
 {
   GRL_DEBUG ("start_multiple_search_operation");
@@ -186,12 +184,12 @@ start_multiple_search_operation (guint search_id,
   iter_skips = (GList *) skip_counts;
   n = 0;
   while (iter_sources) {
-    GrlMediaSource *source;
+    GrlSource *source;
     guint c, id;
     struct ResultCount *rc;
     guint skip;
 
-    source = GRL_MEDIA_SOURCE (iter_sources->data);
+    source = GRL_SOURCE (iter_sources->data);
 
     /* c is the count to use for this source */
     c = (n == 0) ? first_count : individual_count;
@@ -215,13 +213,13 @@ start_multiple_search_operation (guint search_id,
 	skip = 0;
       }
 
-      source_caps = grl_source_get_caps (GRL_SOURCE (source), GRL_OP_SEARCH);
+      source_caps = grl_source_get_caps (source, GRL_OP_SEARCH);
       grl_operation_options_obey_caps (options, source_caps, &source_options, NULL);
       grl_operation_options_set_skip (source_options, skip);
       grl_operation_options_set_count (source_options, rc->count);
 
       /* Execute the search on this source */
-      id = grl_media_source_search (source,
+      id = grl_source_search (source,
 				    msd->text,
 				    msd->keys,
 				    source_options,
@@ -260,14 +258,14 @@ chain_multiple_search_operation (struct MultipleSearchData *old_msd)
   GList *skip_list = NULL;
   GList *source_iter;
   struct ResultCount *rc;
-  GrlMediaSource *source;
+  GrlSource *source;
   struct MultipleSearchData *msd;
 
   /* Compute skip parameter for each of the sources that can still
      provide more results */
   source_iter = old_msd->sources_more;
   while (source_iter) {
-    source = GRL_MEDIA_SOURCE (source_iter->data);
+    source = GRL_SOURCE (source_iter->data);
     rc = (struct ResultCount *)
       g_hash_table_lookup (old_msd->table, (gpointer) source);
     skip_list = g_list_prepend (skip_list,
@@ -294,7 +292,7 @@ chain_multiple_search_operation (struct MultipleSearchData *old_msd)
 }
 
 static void
-multiple_result_async_cb (GrlMediaSource *source,
+multiple_result_async_cb (GrlSource *source,
                           guint op_id,
                           GrlMedia *media,
                           guint remaining,
@@ -327,14 +325,14 @@ multiple_result_async_cb (GrlMediaSource *source,
 }
 
 static void
-multiple_search_cb (GrlMediaSource *source,
+multiple_search_cb (GrlSource *source,
 		    guint search_id,
 		    GrlMedia *media,
 		    guint remaining,
 		    gpointer user_data,
 		    const GError *error)
 {
-  GRL_DEBUG ("multiple_search_cb");
+  GRL_DEBUG (__FUNCTION__);
 
   struct MultipleSearchData *msd;
   gboolean emit;
@@ -452,7 +450,6 @@ multiple_search_cb (GrlMediaSource *source,
 
  operation_done:
   GRL_DEBUG ("Multiple operation finished (%u)", msd->search_id);
-
   grl_operation_remove (msd->search_id);
 }
 
@@ -465,11 +462,11 @@ free_media_from_uri_data (struct MediaFromUriCallbackData *mfucd)
 }
 
 static void
-media_from_uri_cb (GrlMediaSource *source,
+media_from_uri_cb (GrlSource *source,
                    guint operation_id,
-		   GrlMedia *media,
-		   gpointer user_data,
-		   const GError *error)
+				   GrlMedia *media,
+				   gpointer user_data,
+				   const GError *error)
 {
   struct MediaFromUriCallbackData *mfucd =
     (struct MediaFromUriCallbackData *) user_data;
@@ -495,8 +492,8 @@ media_from_uri_cb (GrlMediaSource *source,
 
 /**
  * grl_multiple_search:
- * @sources: (element-type Grl.MediaSource) (allow-none):
- * a #GList of #GrlMediaSource<!-- -->s to search from (%NULL for all
+ * @sources: (element-type Grl.Source) (allow-none):
+ * a #GList of #GrlSource<!-- -->s to search from (%NULL for all
  * searchable sources)
  * @text: the text to search for
  * @keys: (element-type GrlKeyID): the #GList of
@@ -508,7 +505,7 @@ media_from_uri_cb (GrlMediaSource *source,
  * Search for @text in all the sources specified in @sources.
  *
  * If @text is @NULL then NULL-text searchs will be used for each searchable
- * plugin (see #grl_media_source_search for more details).
+ * plugin (see #grl_source_search for more details).
  *
  * This method is asynchronous.
  *
@@ -521,7 +518,7 @@ grl_multiple_search (const GList *sources,
 		     const gchar *text,
 		     const GList *keys,
 		     GrlOperationOptions *options,
-		     GrlMediaSourceResultCb callback,
+		     GrlSourceResultCb callback,
 		     gpointer user_data)
 {
   GrlPluginRegistry *registry;
@@ -533,7 +530,7 @@ grl_multiple_search (const GList *sources,
   GRL_DEBUG ("grl_multiple_search");
 
   g_return_val_if_fail (callback != NULL, 0);
-  g_return_val_if_fail (grl_operation_options_get_count (options) != 0, 0);
+  g_return_val_if_fail (GRL_IS_OPERATION_OPTIONS (options), 0);
 
   /* If no sources have been provided then get the list of all
      searchable sources from the registry */
@@ -598,8 +595,8 @@ multiple_search_cancel_cb (struct MultipleSearchData *msd)
 
 /**
  * grl_multiple_search_sync:
- * @sources: (element-type Grl.MediaSource) (allow-none):
- * a #GList of #GrlMediaSource<!-- -->s where to search from (%NULL for all
+ * @sources: (element-type Grl.Source) (allow-none):
+ * a #GList of #GrlSource<!-- -->s where to search from (%NULL for all
  * available sources with search capability)
  * @text: the text to search for
  * @keys: (element-type GrlKeyID): the #GList of
@@ -668,10 +665,10 @@ grl_multiple_search_sync (const GList *sources,
  */
 void
 grl_multiple_get_media_from_uri (const gchar *uri,
-				 const GList *keys,
-				 GrlOperationOptions *options,
-				 GrlMediaSourceMetadataCb callback,
-				 gpointer user_data)
+				      const GList *keys,
+				      GrlOperationOptions *options,
+				      GrlSourceResolveCb callback,
+				      gpointer user_data)
 {
   GrlPluginRegistry *registry;
   GList *sources, *iter;
@@ -680,6 +677,7 @@ grl_multiple_get_media_from_uri (const gchar *uri,
   g_return_if_fail (uri != NULL);
   g_return_if_fail (keys != NULL);
   g_return_if_fail (callback != NULL);
+  g_return_if_fail (GRL_IS_OPERATION_OPTIONS (options));
 
   registry = grl_plugin_registry_get_default ();
   sources =
@@ -690,8 +688,8 @@ grl_multiple_get_media_from_uri (const gchar *uri,
   /* Look for the first source that knows how to deal with 'uri' */
   iter = sources;
   while (iter && !found) {
-    GrlMediaSource *source = GRL_MEDIA_SOURCE (iter->data);
-    if (grl_media_source_test_media_from_uri (source, uri)) {
+    GrlSource *source = GRL_SOURCE (iter->data);
+    if (grl_source_test_media_from_uri (source, uri)) {
       struct MediaFromUriCallbackData *mfucd =
 	g_new0 (struct MediaFromUriCallbackData, 1);
 
@@ -699,7 +697,7 @@ grl_multiple_get_media_from_uri (const gchar *uri,
       mfucd->user_data = user_data;
       mfucd->uri = g_strdup (uri);
 
-      grl_media_source_get_media_from_uri (source,
+      grl_source_get_media_from_uri (source,
 					   uri,
 					   keys,
 					   options,
diff --git a/src/grl-multiple.h b/src/grl-multiple.h
index cb7bd44..dc29bb8 100644
--- a/src/grl-multiple.h
+++ b/src/grl-multiple.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2011 Igalia S.L.
+ * Copyright (C) 2010 Igalia S.L.
  *
  * Contact: Iago Toral Quiroga <itoral igalia com>
  *
@@ -29,14 +29,14 @@
 
 #include <glib.h>
 
-#include "grl-media-source.h"
+#include "grl-source.h"
 #include "grl-operation-options.h"
 
 guint grl_multiple_search (const GList *sources,
 			   const gchar *text,
 			   const GList *keys,
 			   GrlOperationOptions *options,
-			   GrlMediaSourceResultCb callback,
+			   GrlSourceResultCb callback,
 			   gpointer user_data);
 
 GList *grl_multiple_search_sync (const GList *sources,
@@ -48,7 +48,7 @@ GList *grl_multiple_search_sync (const GList *sources,
 void grl_multiple_get_media_from_uri (const gchar *uri,
 				      const GList *keys,
 				      GrlOperationOptions *options,
-				      GrlMediaSourceMetadataCb callback,
+				      GrlSourceResolveCb callback,
 				      gpointer user_data);
 
 #endif
diff --git a/src/grl-operation-options.c b/src/grl-operation-options.c
index 9f681d8..b440f82 100644
--- a/src/grl-operation-options.c
+++ b/src/grl-operation-options.c
@@ -419,7 +419,7 @@ grl_operation_options_get_count (GrlOperationOptions *options)
  */
 gboolean
 grl_operation_options_set_flags (GrlOperationOptions *options,
-                                 GrlMetadataResolutionFlags flags)
+                                 GrlResolutionFlags flags)
 {
   GValue value = { 0, };
 
@@ -440,7 +440,7 @@ grl_operation_options_set_flags (GrlOperationOptions *options,
  * Returns: resolution flags of @options.
  *
  */
-GrlMetadataResolutionFlags
+GrlResolutionFlags
 grl_operation_options_get_flags (GrlOperationOptions *options)
 {
   const GValue *value  = g_hash_table_lookup (options->priv->data,
diff --git a/src/grl-operation-options.h b/src/grl-operation-options.h
index fdf6f57..f06ecab 100644
--- a/src/grl-operation-options.h
+++ b/src/grl-operation-options.h
@@ -58,20 +58,32 @@ typedef struct {
 #define GRL_OPERATION_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRL_OPERATION_OPTIONS_TYPE, GrlOperationOptionsClass))
 
 /**
- * GrlMetadataResolutionFlags:
+ * GrlResolutionFlags:
  * @GRL_RESOLVE_NORMAL: Normal mode.
  * @GRL_RESOLVE_FULL: Try other plugins if necessary.
  * @GRL_RESOLVE_IDLE_RELAY: Use idle loop to relay results.
  * @GRL_RESOLVE_FAST_ONLY: Only resolve fast metadata keys.
  *
- * GrlMetadata resolution flags
+ * Resolution flags
  */
 typedef enum {
   GRL_RESOLVE_NORMAL     = 0,        /* Normal mode */
   GRL_RESOLVE_FULL       = (1 << 0), /* Try other plugins if necessary */
   GRL_RESOLVE_IDLE_RELAY = (1 << 1), /* Use idle loop to relay results */
   GRL_RESOLVE_FAST_ONLY  = (1 << 2)  /* Only resolve fast metadata keys */
-} GrlMetadataResolutionFlags;
+} GrlResolutionFlags;
+
+/**
+ * GrlWriteFlags:
+ * @GRL_WRITE_NORMAL: Normal mode.
+ * @GRL_WRITE_FULL: Try other plugins if necessary.
+ *
+ * Flags for writing operations.
+ */
+typedef enum {
+  GRL_WRITE_NORMAL     = 0,        /* Normal mode */
+  GRL_WRITE_FULL       = (1 << 0)  /* Try other plugins if necessary */
+} GrlWriteFlags;
 
 #define GRL_COUNT_INFINITY (-1)
 
@@ -93,8 +105,8 @@ gboolean grl_operation_options_set_count (GrlOperationOptions *options, gint cou
 gint grl_operation_options_get_count (GrlOperationOptions *options);
 
 gboolean grl_operation_options_set_flags (GrlOperationOptions *options,
-                                      GrlMetadataResolutionFlags flags);
-GrlMetadataResolutionFlags
+                                      GrlResolutionFlags flags);
+GrlResolutionFlags
     grl_operation_options_get_flags (GrlOperationOptions *options);
 
 gboolean grl_operation_options_set_type_filter (GrlOperationOptions *options,
diff --git a/src/grl-plugin-registry.h b/src/grl-plugin-registry.h
index 8b8e890..7079523 100644
--- a/src/grl-plugin-registry.h
+++ b/src/grl-plugin-registry.h
@@ -31,7 +31,7 @@
 #include <glib-object.h>
 #include <gmodule.h>
 
-#include <grl-media-source.h>
+#include <grl-source.h>
 #include <grl-metadata-key.h>
 #include <grl-config.h>
 #include <grl-definitions.h>
@@ -141,7 +141,7 @@ typedef enum {
   GRL_RANK_LOW     = -32,
   GRL_RANK_DEFAULT =   0,
   GRL_RANK_HIGH    =  32,
-  GRL_RANK_HIGHEST =  64,
+  GRL_RANK_HIGHEST =  64
 } GrlRank;
 
 /* GrlPluginRegistry object */
diff --git a/src/grl-plugin.c b/src/grl-plugin.c
index 4f70a63..aa8e7b8 100644
--- a/src/grl-plugin.c
+++ b/src/grl-plugin.c
@@ -51,7 +51,7 @@ GRL_LOG_DOMAIN(plugin_log_domain);
 enum {
   PROP_0,
   PROP_LOADED,
-  PROP_LAST,
+  PROP_LAST
 };
 
 static GParamSpec *properties[PROP_LAST] = { 0 };
diff --git a/src/grl-source.c b/src/grl-source.c
index d15dde9..71046dc 100644
--- a/src/grl-source.c
+++ b/src/grl-source.c
@@ -23,7 +23,7 @@
 /**
  * SECTION:grl-source
  * @short_description: Abstract base class for sources
- * @see_also: #GrlPlugin, #GrlMediaSource, #GrlMetadataSource, #GrlMedia
+ * @see_also: #GrlPlugin, #GrlSource, #GrlMedia
  *
  * GrlSource is the abstract base class needed to construct a source providing
  * multimedia information that can be used in a Grilo application.
@@ -33,10 +33,11 @@
  */
 
 #include "grl-source.h"
-#include "grl-source-priv.h"
-#include "grl-metadata-source.h"
+
 #include "grl-operation.h"
 #include "grl-operation-priv.h"
+#include "grl-marshal.h"
+#include "grl-type-builtins.h"
 #include "grl-sync-priv.h"
 #include "grl-plugin-registry.h"
 #include "grl-error.h"
@@ -48,9 +49,9 @@
 #define GRL_LOG_DOMAIN_DEFAULT  source_log_domain
 GRL_LOG_DOMAIN(source_log_domain);
 
-#define GRL_SOURCE_GET_PRIVATE(object)                          \
-  (G_TYPE_INSTANCE_GET_PRIVATE((object),                        \
-                               GRL_TYPE_SOURCE,                 \
+#define GRL_SOURCE_GET_PRIVATE(object)             \
+  (G_TYPE_INSTANCE_GET_PRIVATE((object),           \
+                               GRL_TYPE_SOURCE,    \
                                GrlSourcePrivate))
 
 enum {
@@ -60,28 +61,155 @@ enum {
   PROP_DESC,
   PROP_PLUGIN,
   PROP_RANK,
+  PROP_AUTO_SPLIT_THRESHOLD
+};
+
+enum {
+  SIG_CONTENT_CHANGED,
+  SIG_LAST
 };
 
+static gint registry_signals[SIG_LAST];
+
+typedef void (*MediaDecorateCb) (GrlMedia *media,
+                                 gpointer user_data,
+                                 const GError *error);
+
 struct _GrlSourcePrivate {
   gchar *id;
   gchar *name;
   gchar *desc;
   gint rank;
+  guint auto_split_threshold;
   GrlPlugin *plugin;
 };
 
+typedef struct {
+  GrlMedia *media;
+  gboolean is_ready;
+  gint remaining;
+  GError *error;
+} QueueElement;
+
+typedef struct {
+  GrlSource *source;
+  GList *required_keys;
+  gboolean being_queried;
+} MapNode;
+
+struct AutoSplitCtl {
+  gboolean chunk_first;
+  guint chunk_requested;
+  guint chunk_consumed;
+  guint threshold;
+  guint count;
+  guint total_remaining;
+  guint chunk_remaining;
+};
+
 struct OperationState {
   GrlSource *source;
   guint operation_id;
   gboolean cancelled;
   gboolean completed;
+  gboolean started;
+};
+
+struct ResolveRelayCb {
+  GrlSource *source;
+  GrlSupportedOps operation_type;
+  guint operation_id;
+  GrlMedia *media;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResolveCb user_callback;
+  gpointer user_data;
+  GHashTable *map;
+  GHashTable *resolve_specs;
+  GList *specs_to_invoke;
+  gboolean cancel_invoked;
+  GError *error;
+  union {
+    GrlSourceResolveSpec *res;
+    GrlSourceMediaFromUriSpec *mfu;
+  } spec;
+};
+
+struct BrowseRelayCb {
+  GrlSource *source;
+  GrlSupportedOps operation_type;
+  guint operation_id;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResultCb user_callback;
+  gpointer user_data;
+  union {
+    GrlSourceBrowseSpec *browse;
+    GrlSourceSearchSpec *search;
+    GrlSourceQuerySpec *query;
+  } spec;
+  GQueue *queue;
+  gboolean dispatcher_running;
+  struct AutoSplitCtl *auto_split;
+};
+
+struct RemoveRelayCb {
+  GrlSource *source;
+  GrlMedia *media;
+  GrlSourceRemoveCb user_callback;
+  gpointer user_data;
+  GrlSourceRemoveSpec *spec;
+  GError *error;
+};
+
+struct StoreRelayCb {
+  GrlWriteFlags flags;
+  GrlSourceStoreCb user_callback;
+  gpointer user_data;
+};
+
+struct StoreMetadataRelayCb {
+  GrlSource *source;
+  GrlMedia *media;
+  GHashTable *map;
+  GList *use_sources;
+  GList *failed_keys;
+  GList *specs;
+  GrlSourceStoreCb user_callback;
+  gpointer user_data;
+};
+
+struct ResolveFullResolutionCtlCb {
+  GrlSourceResolveCb user_callback;
+  gpointer user_data;
+  GList *keys;
+  GrlResolutionFlags flags;
+  guint operation_id;
+};
+
+struct ResolveFullResolutionDoneCb {
+  GrlSourceResolveCb user_callback;
+  gpointer user_data;
+  GHashTable *pending_callbacks;
+  gboolean cancelled;
+  GrlSource *source;
+  struct ResolveFullResolutionCtlCb *ctl_info;;
+};
+
+struct MediaDecorateData {
+  GrlSource *source;
+  guint operation_id;
+  GHashTable *pending_callbacks;
+  MediaDecorateCb callback;
+  gboolean cancelled;
+  gpointer user_data;
 };
 
-static void grl_source_finalize (GObject *object);
+static void grl_source_finalize (GObject *plugin);
 
-static void grl_source_dispose (GObject *object);
+static void grl_source_dispose (GObject *objct);
 
-static void grl_source_get_property (GObject *object,
+static void grl_source_get_property (GObject *plugin,
                                      guint prop_id,
                                      GValue *value,
                                      GParamSpec *pspec);
@@ -91,6 +219,33 @@ static void grl_source_set_property (GObject *object,
                                      const GValue *value,
                                      GParamSpec *pspec);
 
+static gboolean browse_idle (gpointer user_data);
+
+static gboolean search_idle (gpointer user_data);
+
+static gboolean query_idle (gpointer user_data);
+
+static void run_store_metadata (GrlSource *source,
+                                GrlMedia *media,
+                                GList *keys,
+                                GrlWriteFlags flags,
+                                GrlSourceStoreCb callback,
+                                gpointer user_data);
+
+static void map_list_nodes_free (GList *nodes);
+
+static void resolve_result_relay_cb (GrlSource *source,
+                                     guint operation_id,
+                                     GrlMedia *media,
+                                     gpointer user_data,
+                                     const GError *error);
+
+static gboolean resolve_idle (gpointer user_data);
+
+static gboolean resolve_all_done (gpointer user_data);
+
+static void source_cancel_cb (struct OperationState *op_state);
+
 /* ================ GrlSource GObject ================ */
 
 G_DEFINE_ABSTRACT_TYPE (GrlSource,
@@ -181,6 +336,58 @@ grl_source_class_init (GrlSourceClass *source_class)
                                                      G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT |
                                                      G_PARAM_STATIC_STRINGS));
+  /**
+   * GrlSource:auto-split-threshold
+   *
+   * Transparently split queries with count requests
+   * bigger than a certain threshold into smaller queries.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_AUTO_SPLIT_THRESHOLD,
+                                   g_param_spec_uint ("auto-split-threshold",
+                                                      "Auto-split threshold",
+                                                      "Threshold to use auto-split of queries",
+                                                      0, G_MAXUINT, 0,
+                                                      G_PARAM_READWRITE |
+                                                      G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GrlSource::content-changed:
+   * @source: source that has changed
+   * @changed_medias: a #GPtrArray with the medias that changed or a common
+   * ancestor of them of type #GrlMediaBox.
+   * @change_type: the kind of change that ocurred
+   * @location_unknown: @TRUE if the change happened in @media itself or in one
+   * of its direct children (when @media is a #GrlMediaBox). @FALSE otherwise
+   *
+   * Signals that the content in the source has changed. @changed_medias is the
+   * list of elements that have changed. Usually these medias are of type
+   * #GrlMediaBox, meaning that the content of that box has changed.
+   *
+   * If @location_unknown is @TRUE it means the source cannot establish where the
+   * change happened: could be either in the box, in any child, or in any other
+   * descendant of the box in the hierarchy.
+   *
+   * Both @change_type and @location_unknown are applied to all elements in the
+   * list.
+   *
+   * For the cases where the source can only signal that a change happened, but
+   * not where, it would use a list with the the root box (@NULL id) and set
+   * location_unknown as @TRUE.
+   */
+  registry_signals[SIG_CONTENT_CHANGED] =
+    g_signal_new("content-changed",
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                 0,
+                 NULL,
+                 NULL,
+                 grl_marshal_VOID__BOXED_ENUM_BOOLEAN,
+                 G_TYPE_NONE,
+                 3,
+                 G_TYPE_PTR_ARRAY,
+                 GRL_TYPE_SOURCE_CHANGE_TYPE,
+                 G_TYPE_BOOLEAN);
 
   g_type_class_add_private (source_class,
                             sizeof (GrlSourcePrivate));
@@ -253,6 +460,9 @@ grl_source_set_property (GObject *object,
   case PROP_RANK:
     source->priv->rank = g_value_get_int (value);
     break;
+  case PROP_AUTO_SPLIT_THRESHOLD:
+    source->priv->auto_split_threshold = g_value_get_uint (value);
+    break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (source, prop_id, pspec);
     break;
@@ -265,7 +475,9 @@ grl_source_get_property (GObject *object,
                          GValue *value,
                          GParamSpec *pspec)
 {
-  GrlSource *source = GRL_SOURCE (object);
+  GrlSource *source;
+
+  source = GRL_SOURCE (object);
 
   switch (prop_id) {
   case PROP_ID:
@@ -283,6 +495,9 @@ grl_source_get_property (GObject *object,
   case PROP_RANK:
     g_value_set_int (value, source->priv->rank);
     break;
+  case PROP_AUTO_SPLIT_THRESHOLD:
+    g_value_set_uint (value, source->priv->auto_split_threshold);
+    break;
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (source, prop_id, pspec);
     break;
@@ -291,6 +506,13 @@ grl_source_get_property (GObject *object,
 
 /* ================ Utilities ================ */
 
+static void
+operation_state_free (struct OperationState *op_state)
+{
+  g_object_unref (op_state->source);
+  g_free (op_state);
+}
+
 /*
  * This method will _intersect two key lists_:
  *
@@ -330,76 +552,66 @@ filter_key_list (GrlSource *source,
   return g_list_reverse (out_source);
 }
 
-/* ================ API ================ */
-
-/**
- * grl_source_supported_keys:
- * @source: a source
- *
- * Get a list of #GrlKeyID, which describe a metadata types that this
- * source can fetch and store.
- *
- * Returns: (element-type GrlKeyID) (transfer none): a #GList with the keys
- */
-const GList *
-grl_source_supported_keys (GrlSource *source)
+static GList *
+filter_known_keys (GrlMedia *media,
+                   GList *keys)
 {
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+  GList *unknown_keys = NULL;
+  GList *k;
 
-  if (GRL_SOURCE_GET_CLASS (source)->supported_keys) {
-    return GRL_SOURCE_GET_CLASS (source)->supported_keys (source);
-  } else {
-    return NULL;
+  if (!media) {
+    return g_list_copy (keys);
   }
-}
-
-/**
- * grl_source_slow_keys:
- * @source: a source
- *
- * Similar to grl_source_supported_keys(), but these keys
- * are marked as slow because of the amount of traffic/processing needed
- * to fetch them.
- *
- * Returns: (element-type GrlKeyID) (transfer none): a #GList with the keys
- */
-const GList *
-grl_source_slow_keys (GrlSource *source)
-{
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
 
-  if (GRL_SOURCE_GET_CLASS (source)->slow_keys) {
-    return GRL_SOURCE_GET_CLASS (source)->slow_keys (source);
-  } else {
-    return NULL;
+  for (k = keys; k; k = g_list_next (k)) {
+    if (!grl_data_has_key (GRL_DATA (media),
+                           GRLPOINTER_TO_KEYID (k->data))) {
+      unknown_keys = g_list_prepend (unknown_keys, k->data);
+    }
   }
+
+  return unknown_keys;
 }
 
-/**
- * grl_source_writable_keys:
- * @source: a source
- *
- * Similar to grl_source_supported_keys(), but these keys
- * are marked as writable, meaning the source allows the client
- * to provide new values for these keys that will be stored permanently.
- *
- * Returns: (element-type GrlKeyID) (transfer none):
- * a #GList with the keys
+/*
+ * Removes all keys from @keys that can't be resolved by any of the sources in
+ * @sourcelist.
  */
-const GList *
-grl_source_writable_keys (GrlSource *source)
+static GList *
+filter_unresolvable_keys (GList *sourcelist, GList **keys)
 {
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+  GList *each_key;
+  GList *delete_key;
+  GList *each_source;
+  gboolean supported;
 
-  if (GRL_SOURCE_GET_CLASS (source)->writable_keys) {
-    return GRL_SOURCE_GET_CLASS (source)->writable_keys (source);
-  } else {
-    return NULL;
+  each_key = *keys;
+  while (each_key) {
+    supported = FALSE;
+
+    for (each_source = sourcelist;
+         each_source;
+         each_source = g_list_next (each_source)) {
+      if (g_list_find ((GList *) grl_source_supported_keys (each_source->data),
+                       each_key->data)) {
+        supported = TRUE;
+        break;
+      }
+    }
+    if (!supported) {
+      delete_key = each_key;
+      each_key = g_list_next (each_key);
+      *keys = g_list_delete_link (*keys, delete_key);
+    } else {
+      each_key = g_list_next (each_key);
+    }
   }
+
+  return *keys;
 }
 
-/**
- * grl_source_filter_supported:
+/*
+ * filter_supported:
  * @source: a source
  * @keys: (element-type GrlKeyID) (transfer container) (allow-none) (inout):
  * the list of keys to filter out
@@ -413,10 +625,10 @@ grl_source_writable_keys (GrlSource *source)
  * if @return_filtered is %TRUE will return the list of removed keys;
  * otherwise %NULL
  */
-GList *
-grl_source_filter_supported (GrlSource *source,
-                             GList **keys,
-                             gboolean return_filtered)
+static GList *
+filter_supported (GrlSource *source,
+                  GList **keys,
+                  gboolean return_filtered)
 {
   const GList *supported_keys;
 
@@ -427,8 +639,8 @@ grl_source_filter_supported (GrlSource *source,
   return filter_key_list (source, keys, return_filtered, (GList *) supported_keys);
 }
 
-/**
- * grl_source_filter_slow:
+/*
+ * filter_slow:
  * @source: a source
  * @keys: (element-type GrlKeyID) (transfer container) (allow-none) (inout):
  * the list of keys to filter out
@@ -443,10 +655,10 @@ grl_source_filter_supported (GrlSource *source,
  * @return_filtered is %TRUE will return the list of slow keys; otherwise
  * %NULL
  */
-GList *
-grl_source_filter_slow (GrlSource *source,
-                        GList **keys,
-                        gboolean return_filtered)
+static GList *
+filter_slow (GrlSource *source,
+             GList **keys,
+             gboolean return_filtered)
 {
   const GList *slow_keys;
   GList *fastest_keys, *tmp;
@@ -468,15 +680,15 @@ grl_source_filter_slow (GrlSource *source,
   }
 }
 
-/**
- * grl_source_filter_writable:
+/*
+ * filter_writable:
  * @source: a source
  * @keys: (element-type GrlKeyID) (transfer container) (allow-none) (inout):
  * the list of keys to filter out
  * @return_filtered: if %TRUE the return value shall be a new list with
  * the non-writable keys
  *
- * Similar to grl_source_filter_supported() but applied to the writable keys in
+ * Similar to filter_supported() but applied to the writable keys in
  * grl_source_writable_keys().
  *
  * Filter the @keys list keeping only those keys that are writtable in
@@ -487,8 +699,8 @@ grl_source_filter_slow (GrlSource *source,
  * if @return_filtered is %TRUE will return the list of non-writtable keys;
  * otherwise %NULL
  */
-GList *
-grl_source_filter_writable (GrlSource *source,
+static GList *
+filter_writable (GrlSource *source,
                             GList **keys,
                             gboolean return_filtered)
 {
@@ -502,137 +714,75 @@ grl_source_filter_writable (GrlSource *source,
   return filter_key_list (source, keys, return_filtered, (GList *) writable_keys);
 }
 
-/**
- * grl_source_get_id:
- * @source: a source
- *
- * Returns: the ID of the @source
- */
-const gchar *
-grl_source_get_id (GrlSource *source)
-{
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
-
-  return source->priv->id;
-}
-
-/**
- * grl_source_get_name:
- * @source: a source
- *
- * Returns: the name of the @source
+/*
+ * Operation states:
+ * - started: The operation has been invoked, but not started (plugin has
+ *            not been invoked) yet.
+ * - finished: We have already emitted the last result to the user
+ * - completed: We have already received the last result in the relay cb
+ *              (If it is finished it is also completed).
+ * - cancelled: Operation valid (not finished) but was cancelled.
+ * - ongoing: if the operation is valid (not finished) and not cancelled.
  */
-const gchar *
-grl_source_get_name (GrlSource *source)
-{
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
 
-  return source->priv->name;
-}
 
-/**
- * grl_source_get_description:
- * @source: a source
+/*
+ * operation_set_started:
  *
- * Returns: the description of the @source
- */
-const gchar *
-grl_source_get_description (GrlSource *source)
+ * Sets operation as started (we have invoked the operation in the plugin).
+ **/
+static void
+operation_set_started (guint operation_id)
 {
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+  struct OperationState *op_state;
 
-  return source->priv->desc;
-}
+  GRL_DEBUG ("%s (%d)", __FUNCTION__, operation_id);
 
-/**
- * grl_source_get_plugin:
- * @source: a source
- *
- * Returns: (transfer none): the plugin this source belongs to
- **/
-GrlPlugin *
-grl_source_get_plugin (GrlSource *source)
-{
-  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+  op_state = grl_operation_get_private_data (operation_id);
 
-  return source->priv->plugin;
+  if (op_state) {
+    op_state->started = TRUE;
+  }
 }
 
-/**
- * grl_source_get_rank:
- * @source: a source
- *
- * Gets the source rank
+/*
+ * operation_is_started:
  *
- * Returns: rank value
+ * Checks if operation has been started (the operation in plugin has been
+ * invoked).
  **/
-gint
-grl_source_get_rank (GrlSource *source)
+static gboolean
+operation_is_started (guint operation_id)
 {
-  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
-
-  return source->priv->rank;
-}
+  struct OperationState *op_state;
 
-/**
- * grl_source_supported_operations:
- * @source: a source
- *
- * By default the derived objects of #GrlSource can only resolve.
- *
- * Returns: (type uint): a bitwise mangle with the supported operations by
- * the source
- */
-GrlSupportedOps
-grl_source_supported_operations (GrlSource *source)
-{
-  g_return_val_if_fail (GRL_IS_SOURCE (source), GRL_OP_NONE);
+  op_state = grl_operation_get_private_data (operation_id);
 
-  if (GRL_SOURCE_GET_CLASS (source)->supported_operations) {
-    return GRL_SOURCE_GET_CLASS (source)->supported_operations (source);
-  } else {
-    return GRL_OP_NONE;
-  }
+  return op_state && op_state->started;
 }
 
 /*
- * Operation states:
- *
- * - finished: We have already emitted the last result to the user
- *
- * - completed: We have already received the last result in the relay
- *              cb (If it is finished it is also completed).
- *
- * - cancelled: Operation valid (not finished) but was cancelled.
- *
- * - ongoing: if the operation is valid (not finished) and not
- *   cancelled.
- */
-
-/*
- * grl_source_set_operation_finished:
+ * operation_set_finished:
  *
  * Sets operation as finished (we have already emitted the last result
  * to the user).
  */
-void
-grl_source_set_operation_finished (GrlSource *source,
-                                   guint operation_id)
+static void
+operation_set_finished (guint operation_id)
 {
-  GRL_DEBUG ("grl_source_set_operation_finished (%d)", operation_id);
+  GRL_DEBUG ("%s (%d)", __FUNCTION__, operation_id);
 
   grl_operation_remove (operation_id);
 }
 
 /*
- * grl_source_operation_is_finished:
+ * operation_is_finished:
  *
  * Checks if operation is finished (we have already emitted the last
  * result to the user).
  */
-gboolean
-grl_source_operation_is_finished (GrlSource *source,
-                                  guint operation_id)
+G_GNUC_UNUSED static gboolean
+operation_is_finished (guint operation_id)
 {
   struct OperationState *op_state;
 
@@ -642,18 +792,17 @@ grl_source_operation_is_finished (GrlSource *source,
 }
 
 /*
- * grl_source_set_operation_completed:
+ * operation_set_completed:
  *
  * Sets the operation as completed (we have already received the last
  * result in the relay cb. If it is finsihed it is also completed).
  */
-void
-grl_source_set_operation_completed (GrlSource *source,
-                                    guint operation_id)
+static void
+operation_set_completed (guint operation_id)
 {
   struct OperationState *op_state;
 
-  GRL_DEBUG ("grl_source_set_operation_completed (%d)", operation_id);
+  GRL_DEBUG ("%s (%d)", __FUNCTION__, operation_id);
 
   op_state = grl_operation_get_private_data (operation_id);
 
@@ -663,15 +812,14 @@ grl_source_set_operation_completed (GrlSource *source,
 }
 
 /*
- * grl_source_operation_is_completed:
+ * operation_is_completed:
  *
  * Checks if operation is completed (we have already received the last
  * result in the relay cb. A finished operation is also a completed
  * operation).
  */
-gboolean
-grl_source_operation_is_completed (GrlSource *source,
-                                   guint operation_id)
+static gboolean
+operation_is_completed (guint operation_id)
 {
   struct OperationState *op_state;
 
@@ -681,18 +829,17 @@ grl_source_operation_is_completed (GrlSource *source,
 }
 
 /*
- * grl_source_set_operation_cancelled:
+ * operation_set_cancelled:
  *
  * Sets the operation as cancelled (a valid operation, i.e., not
  * finished, was cancelled)
  */
-void
-grl_source_set_operation_cancelled (GrlSource *source,
-                                    guint operation_id)
+static void
+operation_set_cancelled (guint operation_id)
 {
   struct OperationState *op_state;
 
-  GRL_DEBUG ("grl_source_set_operation_cancelled (%d)", operation_id);
+  GRL_DEBUG ("%s (%d)", __FUNCTION__, operation_id);
 
   op_state = grl_operation_get_private_data (operation_id);
 
@@ -702,14 +849,13 @@ grl_source_set_operation_cancelled (GrlSource *source,
 }
 
 /*
- * grl_source_operation_is_cancelled:
+ * operation_is_cancelled:
  *
  * Checks if operation is cancelled (a valid operation that was
  * cancelled).
  */
-gboolean
-grl_source_operation_is_cancelled (GrlSource *source,
-                                   guint operation_id)
+static gboolean
+operation_is_cancelled (guint operation_id)
 {
   struct OperationState *op_state;
 
@@ -718,13 +864,51 @@ grl_source_operation_is_cancelled (GrlSource *source,
   return op_state && op_state->cancelled;
 }
 
+/*
+ * operation_set_ongoing:
+ *
+ * Sets the operation as ongoing (operation is valid, not finished, not started
+ * and not cancelled)
+ */
 static void
-grl_source_cancel_cb (struct OperationState *op_state)
+operation_set_ongoing (GrlSource *source, guint operation_id)
 {
-  GrlSource *source = op_state->source;
+  struct OperationState *op_state;
 
-  if (!grl_source_operation_is_ongoing (source,
-                                        op_state->operation_id)) {
+  GRL_DEBUG ("%s (%d)", __FUNCTION__, operation_id);
+
+  op_state = g_new0 (struct OperationState, 1);
+  op_state->source = g_object_ref (source);
+  op_state->operation_id = operation_id;
+
+  grl_operation_set_private_data (operation_id,
+                                  op_state,
+                                  (GrlOperationCancelCb) source_cancel_cb,
+                                  (GDestroyNotify) operation_state_free);
+}
+
+/*
+ * operation_is_ongoing:
+ *
+ * Checks if operation is ongoing (operation is valid, and it is not
+ * finished nor cancelled).
+ */
+static gboolean
+operation_is_ongoing (guint operation_id)
+{
+  struct OperationState *op_state;
+
+  op_state = grl_operation_get_private_data (operation_id);
+
+  return op_state && !op_state->cancelled;
+}
+
+static void
+source_cancel_cb (struct OperationState *op_state)
+{
+  GrlSource *source = op_state->source;
+
+  if (!operation_is_ongoing (op_state->operation_id)) {
     GRL_DEBUG ("Tried to cancel invalid or already cancelled operation. "
                "Skipping...");
     return;
@@ -738,8 +922,7 @@ grl_source_cancel_cb (struct OperationState *op_state)
      search(), this will happen when it emits remaining = 0 (which can be
      because it did not cancel the op or because it managed to cancel it and is
      signaling so) */
-  grl_source_set_operation_cancelled (source,
-                                      op_state->operation_id);
+  operation_set_cancelled (op_state->operation_id);
 
   /* If the source provides an implementation for operation cancellation,
      let's use that to avoid further unnecessary processing in the plugin */
@@ -749,47 +932,3370 @@ grl_source_cancel_cb (struct OperationState *op_state)
   }
 }
 
+static void
+cancel_resolve (gpointer source, gpointer operation_id, gpointer user_data)
+{
+  struct OperationState *op_state;
+
+  op_state = grl_operation_get_private_data (GPOINTER_TO_UINT (operation_id));
+  if (op_state) {
+    source_cancel_cb (op_state);
+  }
+}
+
+static void
+resolve_spec_free (GrlSourceResolveSpec *spec)
+{
+  g_object_unref (spec->source);
+  g_object_unref (spec->media);
+  g_object_unref (spec->options);
+  g_free (spec);
+}
+
+static void
+browse_relay_spec_free (struct BrowseRelayCb *brc)
+{
+  switch (brc->operation_type) {
+  case GRL_OP_BROWSE:
+    g_object_unref (brc->spec.browse->source);
+    g_object_unref (brc->spec.browse->container);
+    g_object_unref (brc->spec.browse->options);
+    g_free (brc->spec.browse);
+    break;
+  case GRL_OP_SEARCH:
+    g_object_unref (brc->spec.search->source);
+    g_object_unref (brc->spec.search->options);
+    g_free (brc->spec.search->text);
+    g_free (brc->spec.search);
+    break;
+  case GRL_OP_QUERY:
+    g_object_unref (brc->spec.query->source);
+    g_object_unref (brc->spec.query->options);
+    g_free (brc->spec.query->query);
+    g_free (brc->spec.query);
+    break;
+  default:
+    g_assert_not_reached ();
+    break;
+  }
+}
+
+static void
+media_from_uri_spec_free (GrlSourceMediaFromUriSpec *spec)
+{
+  g_object_unref (spec->source);
+  g_free (spec->uri);
+  g_free (spec);
+}
+
+static void
+store_spec_free (GrlSourceStoreSpec *spec)
+{
+  g_object_unref (spec->source);
+  g_object_unref (spec->media);
+  if (spec->parent) {
+    g_object_unref (spec->parent);
+  }
+  g_free (spec);
+}
+
+static void
+store_metadata_spec_free (GrlSourceStoreMetadataSpec *spec)
+{
+  g_object_unref (spec->source);
+  g_object_unref (spec->media);
+  g_free (spec);
+}
+
+static void
+resolve_relay_free (struct ResolveRelayCb *rrc)
+{
+  GHashTableIter iter;
+  gpointer value;
+
+  g_object_unref (rrc->source);
+  g_object_unref (rrc->media);
+  g_object_unref (rrc->options);
+  g_list_free (rrc->keys);
+
+  g_hash_table_iter_init (&iter, rrc->map);
+  while (g_hash_table_iter_next (&iter, NULL, &value)) {
+    map_list_nodes_free ((GList *) value);
+  }
+  g_hash_table_unref (rrc->map);
+  g_hash_table_unref (rrc->resolve_specs);
+
+  g_slice_free (struct ResolveRelayCb, rrc);
+}
+
+static void
+browse_relay_free (struct BrowseRelayCb *brc)
+{
+  g_object_unref (brc->source);
+  g_object_unref (brc->options);
+  g_list_free (brc->keys);
+  if (brc->auto_split) {
+    g_slice_free (struct AutoSplitCtl, brc->auto_split);
+  }
+  if (brc->queue) {
+    g_queue_free (brc->queue);
+  }
+  g_slice_free (struct BrowseRelayCb, brc);
+}
+
+static void
+remove_relay_free (struct RemoveRelayCb *rrc)
+{
+  g_object_unref (rrc->source);
+  g_object_unref (rrc->media);
+  if (rrc->spec) {
+    g_object_unref (rrc->spec->source);
+    g_object_unref (rrc->spec->media);
+    g_free (rrc->spec->media_id);
+    g_free (rrc->spec);
+  }
+  g_slice_free (struct RemoveRelayCb, rrc);
+}
+
+static void
+store_relay_free (struct StoreRelayCb *src)
+{
+  g_slice_free (struct StoreRelayCb, src);
+}
+
+static void
+store_metadata_relay_free (struct StoreMetadataRelayCb *smrc)
+{
+  g_object_unref (smrc->source);
+  g_object_unref (smrc->media);
+  g_list_free (smrc->failed_keys);
+  g_hash_table_unref (smrc->map);
+  g_list_free (smrc->use_sources);
+  g_list_foreach (smrc->specs, (GFunc) store_metadata_spec_free, NULL);
+
+  g_slice_free (struct StoreMetadataRelayCb, smrc);
+}
+
+/*
+ * Returns a list of all the keys that are in deps but are not defined in data
+ */
+static GList *
+missing_in_data (GrlData *data, const GList *deps)
+{
+  GList *iter, *result = NULL;
+  GRL_DEBUG ("missing_in_data");
+
+  if (!data)
+    return g_list_copy ((GList *) deps);
+
+  for (iter = (GList *)deps; iter; iter = g_list_next (iter)) {
+    if (!grl_data_has_key (data, GRLPOINTER_TO_KEYID (iter->data)))
+      result = g_list_append (result, iter->data);
+  }
+
+  return result;
+}
+
+/*
+ * TRUE iff source supports all those keys
+ */
+static gboolean
+source_supports (GrlSource *source,
+                 const GList *keys)
+{
+  const GList *iter;
+  GList *supported;
+
+  supported = (GList *) grl_source_supported_keys (source);
+
+  for (iter = keys; iter; iter = g_list_next (iter)) {
+    if (!g_list_find (supported, iter->data)) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
 /*
- * grl_source_set_operation_ongoing:
+ * Find the source that should be queried to add @key to @media.
+ * If @additional_keys is provided, the result may include sources that need
+ * more metadata to be present in @media, the keys corresponding to that
+ * metadata will be put in @additional_keys.
+ * If @additional_keys is NULL, will only consider sources that can resolve
+ * @keys immediately
+ *
+ * If @main_source_is_only_resolver is TRUE and @additional_keys is not @NULL,
+ * only additional keys that can be resolved directly by @source will be
+ * considered. Sources that need other additional keys will not be put in the
+ * returned list.
  *
- * Sets the operation as ongoing (operation is valid, not finished and
- * not cancelled)
+ * @source will never be considered as additional source.
+ *
+ * @source and @additional_keys may not be @NULL if
+ * @main_source_is_only_resolver is @TRUE.
+ *
+ * Assumes @key is not already in @media.
  */
-void
-grl_source_set_operation_ongoing (GrlSource *source,
-                                  guint operation_id)
+static GrlSource *
+get_additional_source_for_key (GrlSource *source,
+                               GList *sources,
+                               GrlMedia *media,
+                               GrlKeyID key,
+                               GList **additional_keys,
+                               gboolean main_source_is_only_resolver)
 {
-  struct OperationState *op_state;
+  GList *iter;
 
-  GRL_DEBUG ("grl_source_set_operation_ongoing (%d)", operation_id);
+  g_return_val_if_fail (source || !main_source_is_only_resolver, NULL);
+  g_return_val_if_fail (additional_keys || !main_source_is_only_resolver, NULL);
 
-  op_state = g_new0 (struct OperationState, 1);
-  op_state->source = source;
-  op_state->operation_id = operation_id;
+  for (iter = sources; iter; iter = g_list_next (iter)) {
+    GList *_additional_keys = NULL;
+    GrlSource *_source = (GrlSource *) iter->data;
 
-  grl_operation_set_private_data (operation_id,
-                                  op_state,
-                                  (GrlOperationCancelCb) grl_source_cancel_cb,
-                                  g_free);
+    if (grl_source_may_resolve (_source, media, key, &_additional_keys)) {
+      return _source;
+    }
+
+    if (additional_keys && _additional_keys) {
+      if (main_source_is_only_resolver &&
+          !source_supports (source, _additional_keys))
+        continue;
+
+      *additional_keys = _additional_keys;
+      return _source;
+    }
+  }
+
+  return NULL;
 }
 
 /*
- * grl_source_operation_is_ongoing:
+ * Does the same thing as g_list_concat(), except that elements from
+ * @additional_set that are already in @original_set are destroyed instead of
+ * being added to the result. The same happens for elements that are more than
+ * once in @additional_set.
+ * Because of that, if @original_set does not contain doubles, the result will
+ * not contain doubles.
  *
- * Checks if operation is ongoing (operation is valid, and it is not
- * finished nor cancelled).
+ * You can also use this method to remove doubles from a list like that:
+ * my_list = list_union (NULL, my_list, free_func);
+ *
+ * Note that no elements are copied, elements of @additional_set are either
+ * moved to @original_set or destroyed.
+ * Therefore, both @original_set and @additional_set are modified.
+ *
+ * @free_func is optional.
  */
-gboolean
-grl_source_operation_is_ongoing (GrlSource *source,
-                                 guint operation_id)
+static GList *
+list_union (GList *original_set, GList *additional_set, GDestroyNotify free_func)
 {
-  struct OperationState *op_state;
+  while (additional_set) {
+    /* these two lines pop the first element of additional_set into tmp */
+    GList *tmp = additional_set;
+    additional_set = g_list_remove_link (additional_set, tmp);
 
-  op_state = grl_operation_get_private_data (operation_id);
+    if (NULL == g_list_find (original_set, tmp->data)) {
+      original_set = g_list_concat (original_set, tmp);
+    } else {
+      if (free_func)
+        free_func (tmp->data);
+      g_list_free_1 (tmp);
+    }
+  }
+  return original_set;
+}
 
-  return op_state && !op_state->cancelled;
+/*
+ * Find the sources that should be queried to add @keys to @media.
+ * If @additional_keys is provided, the result may include sources that need
+ * more metadata to be present in @media, the keys corresponding to that
+ * metadata will be put in @additional_keys.
+ * If @additional_keys is NULL, will only consider sources that can resolve
+ * @keys immediately
+ *
+ * If @main_source_is_only_resolver is TRUE and @additional_keys is not @NULL,
+ * only additional keys that can be resolved directly by @source will be
+ * considered. Sources that need other additional keys will not be put in the
+ * returned list.
+ *
+ * Ignore elements of @keys that are already in @media.
+ */
+static GList *
+get_additional_sources (GrlSource *source,
+                        GrlMedia *media,
+                        GList *keys,
+                        GList **additional_keys,
+                        gboolean main_source_is_only_resolver)
+{
+  GList *missing_keys, *iter, *result = NULL, *sources;
+  GrlPluginRegistry *registry;
+
+  missing_keys = missing_in_data (GRL_DATA (media), keys);
+  if (!missing_keys)
+    return NULL;
+
+  registry = grl_plugin_registry_get_default ();
+  sources = grl_plugin_registry_get_sources_by_operations (registry,
+                                                           GRL_OP_RESOLVE,
+                                                           TRUE);
+
+  for (iter = missing_keys; iter; iter = g_list_next (iter)) {
+    GrlKeyID key = (GrlKeyID) iter->data;
+    GrlSource *_source;
+    GList *needed_keys = NULL;
+
+    _source = get_additional_source_for_key (source, sources, media, key,
+                                             additional_keys?&needed_keys:NULL,
+                                             main_source_is_only_resolver);
+    if (_source) {
+      result = g_list_append (result, _source);
+
+      if (needed_keys)
+        *additional_keys = list_union (*additional_keys, needed_keys, NULL);
+
+      GRL_INFO ("%s can resolve %s %s",
+                grl_source_get_name (_source),
+                GRL_METADATA_KEY_GET_NAME (key),
+                needed_keys? "with more keys" : "directly");
+
+    } else {
+      GRL_DEBUG ("Could not find a source for %s",
+                 GRL_METADATA_KEY_GET_NAME (key));
+    }
+  }
+
+  /* list_union() is used to remove doubles */
+  return list_union (NULL, result, NULL);
+}
+
+/**
+ * Will add to @keys the keys that should be asked to @source when doing an
+ * operation with GRL_RESOLVE_FULL.
+ * The added keys are the keys that will be needed by other sources to obtain
+ * the ones that @source says it cannot resolve.
+ */
+static GList *
+expand_operation_keys (GrlSource *source,
+                       GrlMedia *media,
+                       GList *keys)
+{
+  GList *unsupported_keys = NULL,
+    *additional_keys = NULL,
+    *sources;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (!keys)
+    return NULL;
+
+  /* Get the list of keys not supported by the source; they will be queried to
+     other sources */
+  unsupported_keys = filter_supported (source, &keys, TRUE);
+
+  /* now, for each of the unsupported keys to solve
+   * (the ones we know @source cannot resolve), try to find a matching source.
+   * A matching source may need additional keys, but then these additional keys
+   * can be resolved by @source.
+   */
+
+  sources =
+    get_additional_sources (source, media, unsupported_keys,
+                            &additional_keys, TRUE);
+  g_list_free (sources);
+
+  /* Merge back the supported and unsupported list, and add also the additional keys */
+  keys = g_list_concat (keys, unsupported_keys);
+  keys = list_union (keys, additional_keys, NULL);
+
+  return keys;
+}
+
+/*
+ * Returns %TRUE if @key is a slow key for @source
+ */
+static gboolean
+is_slow_key (GrlSource *source, GrlKeyID key)
+{
+  return (g_list_find ((GList *) grl_source_slow_keys (source),
+                       GRLKEYID_TO_POINTER (key)) != NULL);
+}
+
+/*
+ * Create a node for the map keys
+ */
+static MapNode *
+map_node_new (GrlSource *source, GList *keys)
+{
+  MapNode *node = g_new (MapNode, 1);
+  node->source = g_object_ref (source);
+  node->required_keys = g_list_copy (keys);
+  node->being_queried = FALSE;
+
+  return node;
+}
+
+/*
+ * Free a MapNode
+ */
+static void
+map_node_free (MapNode *node)
+{
+  g_object_unref (node->source);
+  g_list_free (node->required_keys);
+  g_free (node);
+}
+
+/*
+ * Free a list of MapNodes
+ */
+static void
+map_list_nodes_free (GList *nodes)
+{
+  g_list_foreach (nodes, (GFunc) map_node_free, NULL);
+  g_list_free (nodes);
+}
+
+/*
+ * Create a new (key, [sources]) map
+ */
+static GHashTable *
+map_keys_new (void)
+{
+  return g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+/*
+ * Maps each key in @keys to the list of sources (from @sources) that can
+ * resolve that key. For each of those (key, source) pair, a list of keys
+ * dependencies is added
+ */
+static void
+map_keys_to_sources (GHashTable *map, GList *keys, GList *sources, GrlMedia *media, gboolean filter_slow_keys)
+{
+  GList *each_source;
+  GList *resolvable_sources;
+  GList *each_key;
+  GList *required_keys;
+  GList *keys_to_map_later = NULL;
+
+  for (each_key = keys;
+       each_key;
+       each_key = g_list_next (each_key)) {
+    if (g_hash_table_lookup_extended (map, each_key->data, NULL, NULL)) {
+      /* Key already in map; skip */
+      continue;
+    }
+    resolvable_sources = NULL;
+    for (each_source = sources;
+         each_source;
+         each_source = g_list_next (each_source)) {
+      if (filter_slow_keys &&
+          is_slow_key (each_source->data, GRLPOINTER_TO_KEYID (each_key->data))) {
+        continue;
+      }
+      required_keys = NULL;
+      if (grl_source_may_resolve (each_source->data,
+                                  media,
+                                  GRLPOINTER_TO_KEYID (each_key->data),
+                                  &required_keys)) {
+        resolvable_sources = g_list_prepend (resolvable_sources, map_node_new (each_source->data, NULL));
+      } else if (required_keys) {
+        resolvable_sources = g_list_prepend (resolvable_sources, map_node_new (each_source->data, required_keys));
+        keys_to_map_later = g_list_concat (keys_to_map_later, required_keys);
+      }
+    }
+
+    resolvable_sources = g_list_reverse (resolvable_sources);
+    g_hash_table_insert (map, each_key->data, resolvable_sources);
+  }
+
+  if (keys_to_map_later) {
+    map_keys_to_sources (map, keys_to_map_later, sources, media, filter_slow_keys);
+    g_list_free (keys_to_map_later);
+  }
+}
+
+/*
+ * Create a new (source, spec) map
+ */
+static GHashTable *
+map_sources_new (void)
+{
+  return g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                g_object_unref,
+                                (GDestroyNotify) resolve_spec_free);
+}
+
+/*
+ * Given a (keys, [sources]) @map, builds a map of sources to
+ * GrlSourceResolveSpec that can solve @key in @media.  Returns @FALSE if the
+ * key can't be mapped (could be, for instance, that it detects a loop)
+ */
+static gboolean
+map_sources_to_specs (GHashTable *specs,
+                      GHashTable *map,
+                      GrlMedia *media,
+                      GrlKeyID key,
+                      GrlOperationOptions *options,
+                      gpointer user_data)
+{
+  GList *map_nodes;
+  MapNode *node;
+  GList *each_required_key;
+  GrlSourceResolveSpec *rs;
+  gboolean success;
+
+  /* Search the source candidate to solve the key */
+  map_nodes = g_hash_table_lookup (map, GRLKEYID_TO_POINTER (key));
+  while (map_nodes) {
+    node = (MapNode *) map_nodes->data;
+    if (node->being_queried) {
+      /* If it has required keys and it is marked as being queried, it means we
+         have enter in a loop */
+      return (node->required_keys == NULL);
+    }
+    /* Check if it has any dependency */
+    if (node->required_keys) {
+      /* Mark node to avoid endless loops */
+      node->being_queried = TRUE;
+      success = TRUE;
+      for (each_required_key = node->required_keys;
+           each_required_key;
+           each_required_key = g_list_next (each_required_key)) {
+        success = map_sources_to_specs (specs, map, media,
+                                        GRLPOINTER_TO_KEYID (each_required_key->data),
+                                        options, user_data);
+        if (!success) {
+          break;
+        }
+      }
+      node->being_queried = FALSE;
+      if (success) {
+        return TRUE;
+      } else {
+        /* Try next node */
+        map_nodes = g_list_next (map_nodes);
+      }
+    } else {
+      rs = g_hash_table_lookup (specs, node->source);
+      if (!rs) {
+        /* Build spec */
+        rs = g_new (GrlSourceResolveSpec, 1);
+        rs->source = g_object_ref (node->source);
+        rs->media = g_object_ref (media);
+        rs->operation_id = grl_operation_generate_id ();
+        rs->keys = g_list_prepend (NULL, GRLKEYID_TO_POINTER (key));
+        rs->options = g_object_ref (options);
+        rs->callback = resolve_result_relay_cb;
+        rs->user_data = user_data;
+        g_hash_table_insert (specs, g_object_ref (node->source), rs);
+      } else {
+        /* Put key in spec */
+        rs->keys = g_list_prepend (rs->keys, GRLKEYID_TO_POINTER (key));
+      }
+      node->being_queried = TRUE;
+    }
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/*
+ * Update @map knowing @key is known; means dropping the @key from the map and
+ * updating all keys that were depending on @key.
+ */
+static void
+map_update_known_key (GHashTable *map, GrlKeyID key, GrlMedia *media)
+{
+  GList *keylist;
+  GList *each_key;
+  GList *map_nodes;
+  GList *each_node;
+  MapNode *node;
+
+  map_list_nodes_free (g_hash_table_lookup (map, GRLKEYID_TO_POINTER (key)));
+  g_hash_table_remove (map, GRLKEYID_TO_POINTER (key));
+
+  keylist = g_hash_table_get_keys (map);
+  for (each_key = keylist;
+       each_key;
+       each_key = g_list_next (each_key)) {
+    map_nodes = g_hash_table_lookup (map, each_key->data);
+    for (each_node = map_nodes;
+         each_node;
+         each_node = g_list_next (each_node)) {
+      node = (MapNode *) each_node->data;
+      if (g_list_find (node->required_keys, (gconstpointer) GRLKEYID_TO_POINTER (key))) {
+        /* Let's recompute the required keys */
+        g_list_free (node->required_keys);
+        node->required_keys = NULL;
+        grl_source_may_resolve (node->source,
+                                media,
+                                GRLPOINTER_TO_KEYID (each_key->data),
+                                &(node->required_keys));
+      }
+    }
+  }
+  g_list_free (keylist);
+}
+
+/*
+ * Update @map knowing that @key could not be resolved by @source.
+ */
+static void
+map_update_unknown_key (GHashTable *map, GrlKeyID key, GrlSource *source)
+{
+  GList *unsolvable_keys = NULL;
+  GList *each_unsolvable_key;
+  GList *each_key;
+  GList *map_nodes;
+  GList *delete_nodes = NULL;
+  GList *each_node;
+  GList *keylist;
+  MapNode *node;
+
+  map_nodes = g_hash_table_lookup (map, GRLKEYID_TO_POINTER (key));
+  each_node = map_nodes;
+  while (each_node) {
+    node = (MapNode *) each_node->data;
+    if (node->being_queried && node->source == source) {
+      map_nodes = g_list_delete_link (map_nodes, each_node);
+      map_node_free (node);
+      g_hash_table_insert (map, GRLKEYID_TO_POINTER (key), map_nodes);
+      each_node = NULL;
+    } else {
+      each_node = g_list_next (each_node);
+    }
+  }
+
+  /* If @map_nodes is empty, means no source is able to solve this key; so any
+     other (key, source) depending on this key can't neither be solved; so
+     remove them from the map */
+  if (!map_nodes) {
+    unsolvable_keys = g_list_prepend (unsolvable_keys,
+                                      GRLKEYID_TO_POINTER (key));
+    for (each_unsolvable_key = g_list_last (unsolvable_keys);
+         each_unsolvable_key;
+         each_unsolvable_key = g_list_previous (each_unsolvable_key)) {
+      keylist = g_hash_table_get_keys (map);
+      for (each_key = keylist;
+           each_key;
+           each_key = g_list_next (each_key)) {
+        map_nodes = g_hash_table_lookup (map, each_key->data);
+        if (map_nodes) {
+          for (each_node = map_nodes;
+               each_node;
+               each_node = g_list_next (each_node)) {
+            node = (MapNode *) each_node->data;
+            if (g_list_find (node->required_keys, each_unsolvable_key->data)) {
+              /* Put this node for further deletion, as it can't be solved */
+              delete_nodes = g_list_prepend (delete_nodes, node);
+            }
+          }
+          /* Delete nodes */
+          for (each_node = delete_nodes;
+               each_node;
+               each_node = g_list_next (each_node)) {
+            map_nodes = g_list_remove (map_nodes, each_node->data);
+          }
+          g_list_free (delete_nodes);
+          delete_nodes = NULL;
+
+          g_hash_table_insert (map, each_key->data, map_nodes);
+          /* If this key can't be resolved neither, mark it */
+          if (!map_nodes) {
+            unsolvable_keys = g_list_prepend (unsolvable_keys, each_key->data);
+          }
+        }
+      }
+      g_list_free (keylist);
+    }
+    g_list_free (unsolvable_keys);
+  }
 }
 
+static void
+send_decorated_media (GrlMedia *media,
+                      gpointer user_data,
+                      const GError *error)
+{
+  struct ResolveRelayCb *mrc = (struct ResolveRelayCb *) user_data;
+
+  mrc->user_callback (mrc->spec.res->source, mrc->spec.res->operation_id,
+                      media, mrc->user_data, error);
+  resolve_relay_free (mrc);
+}
+
+static void
+media_decorate_cb (GrlSource *source,
+                   guint operation_id,
+                   GrlMedia *media,
+                   gpointer user_data,
+                   const GError *error)
+{
+  struct MediaDecorateData *mdd = (struct MediaDecorateData *) user_data;
+  GError *_error = NULL;
+  GRL_DEBUG (__FUNCTION__);
+
+  if (operation_id > 0) {
+    g_hash_table_remove (mdd->pending_callbacks, source);
+  }
+
+  /* Check if pending resolutions must be cancelled */
+  if (!mdd->cancelled &&
+      operation_is_cancelled (mdd->operation_id)) {
+    mdd->cancelled = TRUE;
+    g_hash_table_foreach (mdd->pending_callbacks, cancel_resolve, NULL);
+  }
+
+  /* If all operations are complete, send the element */
+  if (g_hash_table_size (mdd->pending_callbacks) == 0) {
+    if (mdd->cancelled) {
+      _error = g_error_new (GRL_CORE_ERROR,
+                            GRL_CORE_ERROR_OPERATION_CANCELLED,
+                            "Operation was cancelled");
+    }
+    mdd->callback (media, mdd->user_data, _error);
+    if (_error) {
+      g_error_free (_error);
+    }
+    g_object_unref (mdd->source);
+    g_hash_table_unref (mdd->pending_callbacks);
+    g_slice_free (struct MediaDecorateData, mdd);
+  }
+}
+
+static void
+media_decorate (GrlSource *main_source,
+                guint main_operation_id,
+                GrlMedia *media,
+                GList *keys,
+                GrlOperationOptions *options,
+                MediaDecorateCb callback,
+                gpointer user_data)
+{
+  struct MediaDecorateData *mdd;
+  GList *s, *sources;
+  guint operation_id;
+  GrlOperationOptions *decorate_options;
+  GrlResolutionFlags flags;
+
+  flags = grl_operation_options_get_flags (options);
+  if (flags & GRL_RESOLVE_FULL) {
+    decorate_options = grl_operation_options_copy (options);
+    grl_operation_options_set_flags (decorate_options,
+                                     flags & ~GRL_RESOLVE_FULL);
+  } else {
+    decorate_options = g_object_ref (options);
+  }
+
+  sources = get_additional_sources (main_source, media,
+                                    keys, NULL, FALSE);
+
+  mdd = g_slice_new (struct MediaDecorateData);
+  mdd->source = g_object_ref (main_source);
+  mdd->operation_id = main_operation_id;
+  mdd->callback = callback;
+  mdd->user_data = user_data;
+  mdd->pending_callbacks = g_hash_table_new (g_direct_hash, g_direct_equal);
+  mdd->cancelled = FALSE;
+
+  for (s = sources; s; s = g_list_next (s)) {
+    if (grl_source_supported_operations (s->data) & GRL_OP_RESOLVE) {
+      operation_id = grl_source_resolve (s->data, media, keys, decorate_options,
+                                         media_decorate_cb, mdd);
+      if (operation_id > 0) {
+        g_hash_table_insert (mdd->pending_callbacks,
+                             s->data,
+                             GUINT_TO_POINTER (operation_id));
+      }
+    }
+  }
+
+  /* Check if nobody can solve the keys */
+  if (g_hash_table_size (mdd->pending_callbacks) == 0) {
+    media_decorate_cb (NULL, 0, media, mdd, NULL);
+  }
+
+  g_object_unref (decorate_options);
+  g_list_free (sources);
+}
+
+static void
+media_from_uri_result_relay_cb (GrlSource *source,
+                                guint operation_id,
+                                GrlMedia *media,
+                                gpointer user_data,
+                                const GError *error)
+{
+  struct ResolveRelayCb *rrc = (struct ResolveRelayCb *) user_data;
+  GError *_error = (GError *) error;
+  GList *unknown_keys;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Free specs */
+  media_from_uri_spec_free (rrc->spec.mfu);
+
+  /* Check if cancelled */
+  if (operation_is_cancelled (rrc->operation_id)) {
+    /* if the plugin already set an error, we don't care because we're
+     * cancelled */
+    GRL_DEBUG ("operation was cancelled");
+    _error = g_error_new (GRL_CORE_ERROR,
+                          GRL_CORE_ERROR_OPERATION_CANCELLED,
+                          "Operation was cancelled");
+  }
+
+  if (_error) {
+    rrc->user_callback (source, rrc->operation_id, media, rrc->user_data, _error);
+    if (_error != error) {
+      g_error_free (_error);
+    }
+    resolve_relay_free (rrc);
+    return;
+  }
+
+  if (grl_operation_options_get_flags (rrc->options) & GRL_RESOLVE_FULL) {
+    /* Check if there are unsolved keys that need to be solved by other
+       sources */
+    unknown_keys = filter_known_keys (media, rrc->keys);
+    if (unknown_keys) {
+      media_decorate (source, operation_id, media, unknown_keys, rrc->options,
+                      send_decorated_media, rrc);
+      g_list_free (unknown_keys);
+      return;
+    }
+  }
+
+  rrc->user_callback (source, rrc->operation_id, media, rrc->user_data, error);
+  resolve_relay_free (rrc);
+}
+
+static void
+cancel_resolve_spec (GrlSource *source, GrlSourceResolveSpec *spec)
+{
+  struct OperationState *op_state;
+
+  op_state = grl_operation_get_private_data (spec->operation_id);
+  if (op_state) {
+    source_cancel_cb (op_state);
+  }
+}
+
+static void
+resolve_result_relay_cb (GrlSource *source,
+                         guint operation_id,
+                         GrlMedia *media,
+                         gpointer user_data,
+                         const GError *error)
+{
+  struct ResolveRelayCb *rrc = (struct ResolveRelayCb *) user_data;
+  GList *each_key;
+  GList *delete_key;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (!operation_is_cancelled (operation_id)) {
+    /* Check which keys are now known */
+    each_key = rrc->keys;
+    while (each_key) {
+      if (grl_data_has_key (GRL_DATA (media), GRLPOINTER_TO_KEYID (each_key->data))) {
+        map_update_known_key (rrc->map, GRLPOINTER_TO_KEYID (each_key->data), media);
+        delete_key = each_key;
+        each_key = g_list_next (each_key);
+        rrc->keys = g_list_delete_link (rrc->keys, delete_key);
+      } else {
+        map_update_unknown_key (rrc->map, GRLPOINTER_TO_KEYID (each_key->data), source);
+        each_key = g_list_next (each_key);
+      }
+    }
+
+    g_hash_table_remove (rrc->resolve_specs, source);
+  }
+
+  operation_set_finished (operation_id);
+
+  if (operation_is_cancelled (rrc->operation_id) &&
+      !rrc->cancel_invoked) {
+    rrc->cancel_invoked = TRUE;
+    g_hash_table_foreach (rrc->resolve_specs, (GHFunc) cancel_resolve_spec, NULL);
+  }
+
+  if (error && source == rrc->source && !rrc->error) {
+    /* Save error for further sending */
+    rrc->error = g_error_copy (error);
+  }
+
+  if (g_hash_table_size (rrc->resolve_specs) == 0 && !rrc->specs_to_invoke) {
+    /* All sources have replied. Let's run another round if not cancelled */
+    if (!operation_is_cancelled (rrc->operation_id)) {
+      each_key = rrc->keys;
+      while (each_key) {
+        if (map_sources_to_specs (rrc->resolve_specs, rrc->map, media,
+                                  GRLPOINTER_TO_KEYID (each_key->data),
+                                  rrc->options, rrc)) {
+          each_key = g_list_next (each_key);
+        } else {
+          delete_key = each_key;
+          each_key = g_list_next (each_key);
+          rrc->keys = g_list_delete_link (rrc->keys, delete_key);
+        }
+      }
+
+    }
+
+    rrc->specs_to_invoke = g_hash_table_get_values (rrc->resolve_specs);
+    if (rrc->specs_to_invoke) {
+      g_idle_add_full (grl_operation_options_get_flags (rrc->options) & GRL_RESOLVE_IDLE_RELAY?
+                       G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                       resolve_idle,
+                       rrc,
+                       NULL);
+    } else {
+      g_idle_add_full (grl_operation_options_get_flags (rrc->options) & GRL_RESOLVE_IDLE_RELAY?
+                       G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                       resolve_all_done,
+                       rrc,
+                       NULL);
+    }
+  }
+}
+
+static gboolean
+queue_process (gpointer user_data)
+{
+  QueueElement *qelement;
+  GError *error;
+  gint remaining;
+  struct BrowseRelayCb *brc = (struct BrowseRelayCb *) user_data;
+
+  /* Check if operation is cancelled */
+  if (operation_is_cancelled (brc->operation_id)) {
+    /* This is how this works: if operation is cancelled, no one will add more
+       elements to queue. If one with remaining=0 is found, means that the
+       browse/search/query operation finished before operation is cancelled. So we
+       need to emit this result to user to tell him that operation is cancelled. For
+       elements that are not ready, means that a source_resolve() was run to get
+       solve more keys. So the algorithm is freeing all elements that are ready, and
+       if some of them has remaining==0, sending the cancel signal to user */
+    while ((qelement = (QueueElement *) g_queue_peek_head (brc->queue)) &&
+           qelement->is_ready) {
+      g_queue_pop_head (brc->queue);
+      if (qelement->remaining == 0) {
+        error = g_error_new (GRL_CORE_ERROR,
+                             GRL_CORE_ERROR_OPERATION_CANCELLED,
+                             "Operation was cancelled");
+        brc->user_callback (brc->source, brc->operation_id, NULL,
+                            0, brc->user_data, error);
+        g_error_free (error);
+      }
+      if (qelement->error) {
+        g_error_free (qelement->error);
+      }
+      g_free (qelement);
+    }
+    if (g_queue_is_empty (brc->queue)) {
+      operation_set_finished (brc->operation_id);
+      browse_relay_free (brc);
+      return FALSE;
+    }
+    brc->dispatcher_running = FALSE;
+    return FALSE;
+  }
+
+  /* Send the last element */
+  qelement = (QueueElement *) g_queue_pop_head (brc->queue);
+  remaining = qelement->remaining;
+  brc->user_callback (brc->source, brc->operation_id, qelement->media,
+                      remaining, brc->user_data, qelement->error);
+  if (qelement->error) {
+    g_error_free (qelement->error);
+  }
+  g_free (qelement);
+
+  if (remaining == 0) {
+    operation_set_finished (brc->operation_id);
+    browse_relay_free (brc);
+    return FALSE;
+  }
+
+  /* Check if should keep running */
+  qelement = (QueueElement *) g_queue_peek_head (brc->queue);
+  brc->dispatcher_running = qelement && qelement->is_ready;
+
+  return brc->dispatcher_running;
+}
+
+static void
+queue_start_process (struct BrowseRelayCb *brc)
+{
+  QueueElement *qelement;
+
+  if (!brc->dispatcher_running) {
+    qelement = g_queue_peek_head (brc->queue);
+    if (qelement && qelement->is_ready) {
+      g_idle_add (queue_process,  brc);
+      brc->dispatcher_running = TRUE;
+    }
+  }
+}
+
+static gint
+compare_queue_element (QueueElement *qelement,
+                       GrlMedia *media)
+{
+  return qelement->media < media;
+}
+
+static void
+media_ready_cb (GrlMedia *media,
+                gpointer user_data,
+                const GError *error)
+{
+  GList *element;
+  QueueElement *qelement;
+  struct BrowseRelayCb *brc = (struct BrowseRelayCb *) user_data;
+
+  /* Mark element as ready */
+  element = g_queue_find_custom (brc->queue, media,
+                                 (GCompareFunc) compare_queue_element);
+  if (!element) {
+    GRL_ERROR ("Media not found in the queue!");
+    return;
+  }
+
+  qelement = (QueueElement *) element->data;
+  qelement->is_ready = TRUE;
+  queue_start_process (brc);
+}
+
+static void
+queue_add_media (struct BrowseRelayCb *brc,
+                 GrlMedia *media,
+                 guint remaining,
+                 const GError *error)
+{
+  QueueElement *qelement;
+  GList *unknown_keys;
+
+  if (!brc->queue) {
+    brc->queue = g_queue_new ();
+  }
+
+  /* Add element */
+  qelement = g_new (QueueElement, 1);
+  qelement->media = media;
+  qelement->remaining = remaining;
+  /* Media is ready if we do not need to ask other sources to complete it */
+  qelement->is_ready = TRUE;
+  if (grl_operation_options_get_flags (brc->options) & GRL_RESOLVE_FULL) {
+    unknown_keys = filter_known_keys (media, brc->keys);
+    if (unknown_keys) {
+      qelement->is_ready = FALSE;
+    }
+  }
+  if (error) {
+    qelement->error = g_error_copy (error);
+  } else {
+    qelement->error = NULL;
+  }
+  g_queue_push_tail (brc->queue, qelement);
+
+  if (!qelement->is_ready) {
+    media_decorate (brc->source, brc->operation_id, media, unknown_keys,
+                    brc->options, media_ready_cb, brc);
+  }
+
+  queue_start_process (brc);
+}
+
+static struct AutoSplitCtl *
+auto_split_setup (GrlSource *source,
+                  GrlOperationOptions *options)
+{
+  struct AutoSplitCtl *as_ctl = NULL;
+  gint count = grl_operation_options_get_count (options);
+
+  if (source->priv->auto_split_threshold > 0 &&
+      count > source->priv->auto_split_threshold) {
+    GRL_DEBUG ("auto-split: enabled");
+
+    as_ctl = g_slice_new (struct AutoSplitCtl);
+    as_ctl->threshold = source->priv->auto_split_threshold;
+    as_ctl->total_remaining = count;
+    as_ctl->chunk_remaining = as_ctl->threshold;
+    count = as_ctl->chunk_remaining;
+    grl_operation_options_set_count (options, count);
+    GRL_DEBUG ("auto-split: requesting chunk (skip=%u, count=%u)",
+               grl_operation_options_get_skip (options),
+               count);
+  }
+
+  return as_ctl;
+}
+
+static void
+auto_split_run_next_chunk (struct BrowseRelayCb *brc)
+{
+  brc->auto_split->chunk_remaining = MIN (brc->auto_split->threshold,
+                                          brc->auto_split->total_remaining);
+
+  switch (brc->operation_type) {
+  case GRL_OP_BROWSE:
+    grl_operation_options_set_skip (brc->spec.browse->options,
+                                    grl_operation_options_get_skip (brc->spec.browse->options) +
+                                    brc->auto_split->threshold);
+    grl_operation_options_set_count (brc->spec.browse->options,
+                                     brc->auto_split->chunk_remaining);
+    GRL_DEBUG ("auto-split: requesting chunk (skip=%u, count=%u)",
+               grl_operation_options_get_skip (brc->spec.browse->options),
+               grl_operation_options_get_count (brc->spec.browse->options));
+    g_idle_add_full (grl_operation_options_get_flags (brc->options) & GRL_RESOLVE_IDLE_RELAY?
+                     G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                     browse_idle,
+                     brc->spec.browse,
+                     NULL);
+    break;
+  case GRL_OP_SEARCH:
+    grl_operation_options_set_skip (brc->spec.search->options,
+                                    grl_operation_options_get_skip (brc->spec.search->options) +
+                                    brc->auto_split->threshold);
+    grl_operation_options_set_count (brc->spec.search->options,
+                                     brc->auto_split->chunk_remaining);
+    GRL_DEBUG ("auto-split: requesting chunk (skip=%u, count=%u)",
+               grl_operation_options_get_skip (brc->spec.search->options),
+               grl_operation_options_get_count (brc->spec.search->options));
+    g_idle_add_full (grl_operation_options_get_flags (brc->options) & GRL_RESOLVE_IDLE_RELAY?
+                     G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                     search_idle,
+                     brc->spec.search,
+                     NULL);
+    break;
+  case GRL_OP_QUERY:
+    grl_operation_options_set_skip (brc->spec.query->options,
+                                    grl_operation_options_get_skip (brc->spec.query->options) +
+                                    brc->auto_split->threshold);
+    grl_operation_options_set_count (brc->spec.query->options,
+                                     brc->auto_split->chunk_remaining);
+    GRL_DEBUG ("auto-split: requesting chunk (skip=%u, count=%u)",
+               grl_operation_options_get_skip (brc->spec.query->options),
+               grl_operation_options_get_count (brc->spec.query->options));
+    g_idle_add_full (grl_operation_options_get_flags (brc->options) & GRL_RESOLVE_IDLE_RELAY?
+                     G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                     query_idle,
+                     brc->spec.query,
+                     NULL);
+    break;
+  default:
+    g_assert_not_reached ();
+    break;
+  }
+}
+
+static void
+browse_result_relay_cb (GrlSource *source,
+                        guint operation_id,
+                        GrlMedia *media,
+                        guint remaining,
+                        gpointer user_data,
+                        const GError *error)
+{
+  GError *_error;
+  struct BrowseRelayCb *brc = (struct BrowseRelayCb *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Ignore elements after operation has completed */
+  if (operation_is_completed (operation_id)) {
+    GRL_WARNING ("Source '%s' emitted 'remaining=0' more than once "
+                 "for operation %d",
+                 grl_source_get_id (source), operation_id);
+    if (media) {
+      g_object_unref (media);
+    }
+    return;
+  }
+
+  /* Check if cancelled */
+  if (operation_is_cancelled (operation_id)) {
+    GRL_DEBUG ("Operation is cancelled, skipping result until getting the last one");
+    if (media) {
+      g_object_unref (media);
+    }
+    /* Wait for the last element */
+    if (remaining > 0) {
+      return;
+    } else {
+      _error = g_error_new (GRL_CORE_ERROR,
+                            GRL_CORE_ERROR_OPERATION_CANCELLED,
+                            "Operation was cancelled");
+      brc->user_callback (source, operation_id, NULL, 0,
+                          brc->user_data, _error);
+      g_error_free (_error);
+      goto free_resources;
+    }
+  }
+
+  /* Auto-split management */
+  if (brc->auto_split) {
+    brc->auto_split->chunk_remaining--;
+    brc->auto_split->total_remaining--;
+    /* On last element, check if more elements should be asked: if source
+       satisfied all requested elements, but we need to get more */
+    if (remaining == 0) {
+      if (brc->auto_split->chunk_remaining == 0 &&
+          brc->auto_split->total_remaining > 0) {
+        auto_split_run_next_chunk (brc);
+        remaining = brc->auto_split->total_remaining;
+      }
+    } else {
+      remaining = brc->auto_split->total_remaining;
+    }
+  }
+
+  /* Set the source */
+  if (media) {
+    grl_media_set_source (media, grl_source_get_id (source));
+  }
+
+  /* If we need further processing of media, put it in a queue */
+  if (grl_operation_options_get_flags (brc->options) &
+      (GRL_RESOLVE_FULL | GRL_RESOLVE_IDLE_RELAY)) {
+    queue_add_media (brc, media, remaining, error);
+  } else {
+    brc->user_callback (source, operation_id, media, remaining,
+                        brc->user_data, error);
+  }
+
+  if (remaining == 0) {
+  free_resources:
+    browse_relay_spec_free (brc);
+    if (!brc->queue || g_queue_is_empty (brc->queue)) {
+      operation_set_finished (operation_id);
+      browse_relay_free (brc);
+    } else {
+      /* There are elements pending to be processed; let's wait to free it in
+         the queue */
+      operation_set_completed (operation_id);
+    }
+  }
+}
+
+static void
+remove_result_relay_cb (GrlSource *source,
+                        GrlMedia *media,
+                        gpointer user_data,
+                        const GError *error)
+{
+  struct RemoveRelayCb *rrc = (struct RemoveRelayCb *) user_data;
+
+  rrc->user_callback (source, media, rrc->user_data, error);
+  remove_relay_free (rrc);
+}
+
+static gboolean
+resolve_idle (gpointer user_data)
+{
+  struct ResolveRelayCb *rrc = (struct ResolveRelayCb *) user_data;
+  GrlSourceResolveSpec *rs;
+  GList *spec;
+  GList *key;
+  gboolean run_next;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Abort if operation was cancelled */
+  if (operation_is_cancelled (rrc->operation_id)) {
+    for (spec = rrc->specs_to_invoke;
+         spec;
+         spec = g_list_next (rs)) {
+      rs = (GrlSourceResolveSpec *) spec->data;
+      g_hash_table_remove (rrc->resolve_specs, rs->source);
+    }
+    g_list_free (rrc->specs_to_invoke);
+    rrc->specs_to_invoke = NULL;
+    run_next = FALSE;
+    resolve_result_relay_cb (rrc->source, rrc->operation_id, rrc->media, rrc, NULL);
+  } else {
+    rs = rrc->specs_to_invoke->data;
+    rrc->specs_to_invoke = g_list_next (rrc->specs_to_invoke);
+    run_next = (rrc->specs_to_invoke != NULL);
+
+    /* Put the specific keys in rs also into rrc */
+    for (key = rs->keys; key; key = g_list_next (key)) {
+      if (!g_list_find (rrc->keys, key->data)) {
+        rrc->keys = g_list_prepend (rrc->keys, key->data);
+      }
+    }
+
+    operation_set_ongoing (rs->source, rs->operation_id);
+    operation_set_started (rs->operation_id);
+    GRL_SOURCE_GET_CLASS (rs->source)->resolve (rs->source, rs);
+  }
+
+  return run_next;
+}
+
+static gboolean
+resolve_all_done (gpointer user_data)
+{
+  struct ResolveRelayCb *rrc = (struct ResolveRelayCb *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (operation_is_cancelled (rrc->operation_id)) {
+    if (rrc->error) {
+      g_error_free (rrc->error);
+    }
+    rrc->error = g_error_new (GRL_CORE_ERROR,
+                              GRL_CORE_ERROR_OPERATION_CANCELLED,
+                              "Operation was cancelled");
+  }
+
+  rrc->user_callback (rrc->source, rrc->operation_id, rrc->media, rrc->user_data, rrc->error);
+  operation_set_finished (rrc->operation_id);
+  resolve_relay_free (rrc);
+
+  return FALSE;
+}
+
+static gboolean
+media_from_uri_idle (gpointer user_data)
+{
+  GrlSourceMediaFromUriSpec *mfus = (GrlSourceMediaFromUriSpec *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Abort if operation is cancelled */
+  if (operation_is_cancelled (mfus->operation_id)) {
+    mfus->callback (mfus->source, mfus->operation_id,
+                    NULL, mfus->user_data, NULL);
+  } else {
+    operation_set_started (mfus->operation_id);
+    GRL_SOURCE_GET_CLASS (mfus->source)->media_from_uri (mfus->source, mfus);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+browse_idle (gpointer user_data)
+{
+  GrlSourceBrowseSpec *bs = (GrlSourceBrowseSpec *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Abort if operation is cancelled */
+  if (operation_is_cancelled (bs->operation_id)) {
+    bs->callback (bs->source, bs->operation_id, NULL, 0, bs->user_data, NULL);
+  } else {
+    operation_set_started (bs->operation_id);
+    GRL_SOURCE_GET_CLASS (bs->source)->browse (bs->source, bs);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+search_idle (gpointer user_data)
+{
+  GrlSourceSearchSpec *ss = (GrlSourceSearchSpec *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Abort if operation is cancelled */
+  if (operation_is_cancelled (ss->operation_id)) {
+    ss->callback (ss->source, ss->operation_id, NULL, 0, ss->user_data, NULL);
+  } else {
+    operation_set_started (ss->operation_id);
+    GRL_SOURCE_GET_CLASS (ss->source)->search (ss->source, ss);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+query_idle (gpointer user_data)
+{
+  GrlSourceQuerySpec *qs = (GrlSourceQuerySpec *) user_data;
+  GRL_DEBUG (__FUNCTION__);
+
+  /* Abort if operation is cancelled */
+  if (operation_is_cancelled (qs->operation_id)) {
+    qs->callback (qs->source, qs->operation_id, NULL, 0, qs->user_data, NULL);
+  } else {
+    operation_set_started (qs->operation_id);
+    GRL_SOURCE_GET_CLASS (qs->source)->query (qs->source, qs);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+remove_idle (gpointer user_data)
+{
+  struct RemoveRelayCb *rrc = (struct RemoveRelayCb *) user_data;
+  GRL_DEBUG (__FUNCTION__);
+
+  if (rrc->error) {
+    rrc->user_callback (rrc->source, rrc->media, rrc->user_data, rrc->error);
+    remove_relay_free (rrc);
+  } else {
+    GRL_SOURCE_GET_CLASS (rrc->source)->remove (rrc->source, rrc->spec);
+  }
+
+  return FALSE;
+}
+
+static void
+resolve_result_async_cb (GrlSource *source,
+                         guint operation_id,
+                         GrlMedia *media,
+                         gpointer user_data,
+                         const GError *error)
+{
+  GrlDataSync *ds = (GrlDataSync *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error) {
+    ds->error = g_error_copy (error);
+  }
+
+  ds->data = media;
+  ds->complete = TRUE;
+}
+
+static void
+multiple_result_async_cb (GrlSource *source,
+                          guint op_id,
+                          GrlMedia *media,
+                          guint remaining,
+                          gpointer user_data,
+                          const GError *error)
+{
+  GrlDataSync *ds = (GrlDataSync *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error) {
+    ds->error = g_error_copy (error);
+
+    /* Free previous results */
+    g_list_foreach (ds->data, (GFunc) g_object_unref, NULL);
+    g_list_free (ds->data);
+
+    ds->data = NULL;
+    ds->complete = TRUE;
+    return;
+  }
+
+  if (media) {
+    ds->data = g_list_prepend (ds->data, media);
+  }
+
+  if (remaining == 0) {
+    ds->data = g_list_reverse (ds->data);
+    ds->complete = TRUE;
+  }
+}
+
+static void
+remove_async_cb (GrlSource *source,
+                 GrlMedia *media,
+                 gpointer user_data,
+                 const GError *error)
+{
+  GrlDataSync *ds = (GrlDataSync *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error) {
+    ds->error = g_error_copy (error);
+  }
+
+  ds->complete = TRUE;
+}
+
+static void
+store_result_async_cb (GrlSource *source,
+                       GrlMedia *media,
+                       GList *failed_keys,
+                       gpointer user_data,
+                       const GError *error)
+{
+  GrlDataSync *ds = (GrlDataSync *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error) {
+    ds->error = g_error_copy (error);
+  }
+
+  ds->data = g_list_copy (failed_keys);
+  ds->complete = TRUE;
+}
+
+
+static void
+store_metadata_result_async_cb (GrlSource *source,
+                                GrlMedia *media,
+                                GList *failed_keys,
+                                gpointer user_data,
+                                const GError *error)
+{
+  GrlDataSync *ds = (GrlDataSync *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error) {
+    ds->error = g_error_copy (error);
+  }
+
+  ds->data = g_list_copy (failed_keys);
+  ds->complete = TRUE;
+}
+
+static GHashTable *
+map_writable_keys (GrlSource *source,
+                   GList *keys,
+                   GrlWriteFlags flags,
+                   GList **failed_keys)
+{
+  GHashTable *map;
+  GrlPluginRegistry *registry;
+  GList *sources = NULL;
+  GList *sources_iter;
+  GrlSource *_source;
+
+  map = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                               g_object_unref,
+                               (GDestroyNotify) g_list_free);
+
+  /* 'key_list' holds keys that can be written by this source
+     'unsupportedy_keys' holds those that must be handled by other sources */
+  GList *key_list = g_list_copy (keys);
+  GList *unsupported_keys = filter_writable (source, &key_list, TRUE);
+
+  if (key_list) {
+    g_hash_table_insert (map, g_object_ref (source), key_list);
+  }
+
+  if (!unsupported_keys || !(flags & GRL_WRITE_FULL)) {
+    /* We are done! */
+    goto done;
+  }
+
+  /* Check if other sources can write the missing keys */
+  registry = grl_plugin_registry_get_default ();
+  sources =
+    grl_plugin_registry_get_sources_by_operations (registry,
+                                                   GRL_OP_STORE_METADATA,
+                                                   TRUE);
+
+  for (sources_iter = sources; unsupported_keys && sources_iter;
+       sources_iter = g_list_next (sources_iter)) {
+    _source = GRL_SOURCE (sources_iter->data);
+
+    if (_source == source) {
+      continue;
+    }
+
+    key_list = unsupported_keys;
+    unsupported_keys = filter_writable (_source, &key_list, TRUE);
+
+    if (!key_list) {
+      continue;
+    }
+
+    g_hash_table_insert (map, g_object_ref (_source), key_list);
+
+    if (!unsupported_keys) {
+      break;
+    }
+  }
+
+  g_list_free (sources);
+
+ done:
+  *failed_keys = unsupported_keys;
+  return map;
+}
+
+static void
+store_relay_cb (GrlSource *source,
+                GrlMedia *media,
+                GList *failed_keys,
+                gpointer user_data,
+                const GError *error)
+{
+  GrlSourceStoreSpec *ss  = (GrlSourceStoreSpec *) user_data;
+  struct StoreRelayCb *src = (struct StoreRelayCb *) ss->user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  if (error || src->flags & GRL_WRITE_NORMAL) {
+    src->user_callback (source, media, failed_keys, src->user_data, error);
+  } else {
+    run_store_metadata (source, media, failed_keys, GRL_WRITE_FULL,
+                        src->user_callback, src->user_data);
+  }
+  store_relay_free (src);
+  store_spec_free (ss);
+}
+
+static void
+store_metadata_ctl_cb (GrlSource *source,
+                       GrlMedia *media,
+                       GList *failed_keys,
+                       gpointer user_data,
+                       const GError *error)
+{
+  struct StoreMetadataRelayCb *smrc;
+  GError *own_error = NULL;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  smrc = (struct StoreMetadataRelayCb *) user_data;
+
+  if (failed_keys) {
+    smrc->failed_keys = g_list_concat (smrc->failed_keys, failed_keys);
+  }
+
+  g_hash_table_remove (smrc->map, source);
+
+  /* If we all sources have answered */
+  if (g_hash_table_size (smrc->map) == 0) {
+    /* We ignore the plugin errors, instead we create an own error
+       if some keys were not written */
+    if (smrc->user_callback) {
+      if (smrc->failed_keys) {
+        own_error = g_error_new (GRL_CORE_ERROR,
+                                 GRL_CORE_ERROR_STORE_METADATA_FAILED,
+                                 "Some keys could not be written");
+      }
+      smrc->user_callback (smrc->source,
+                           media,
+                           smrc->failed_keys,
+                           smrc->user_data,
+                           own_error);
+      if (own_error) {
+        g_error_free (own_error);
+      }
+    }
+    store_metadata_relay_free (smrc);
+  }
+}
+
+static gboolean
+store_idle (gpointer user_data)
+{
+  GrlSourceStoreSpec *ss = (GrlSourceStoreSpec *) user_data;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  GRL_SOURCE_GET_CLASS (ss->source)->store(ss->source, ss);
+
+  return FALSE;
+}
+
+static gboolean
+store_metadata_idle (gpointer user_data)
+{
+  GrlSourceStoreMetadataSpec *sms;
+  struct StoreMetadataRelayCb *smrc;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  smrc = (struct StoreMetadataRelayCb *) user_data;
+
+  sms = g_new (GrlSourceStoreMetadataSpec, 1);
+
+  sms->source = g_object_ref (g_list_first (smrc->use_sources));
+  sms->keys = g_hash_table_lookup (smrc->map, sms->source);
+  sms->media = g_object_ref (smrc->media);
+  sms->callback = store_metadata_ctl_cb;
+  sms->user_data = smrc;
+
+  /* Remove list header */
+  smrc->use_sources = g_list_remove_link (smrc->use_sources, smrc->use_sources);
+  smrc->specs = g_list_prepend (smrc->specs, sms);
+
+  GRL_SOURCE_GET_CLASS (sms->source)->store_metadata (sms->source, sms);
+
+  return (smrc->use_sources != NULL);
+}
+
+static void
+run_store_metadata (GrlSource *source,
+                    GrlMedia *media,
+                    GList *keys,
+                    GrlWriteFlags flags,
+                    GrlSourceStoreCb callback,
+                    gpointer user_data)
+{
+  GHashTable *map;
+  GList *failed_keys = NULL;
+  GError *error;
+  struct StoreMetadataRelayCb *smrc;
+
+  map = map_writable_keys (source, keys, flags, &failed_keys);
+
+  if (g_hash_table_size (map) == 0) {
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_STORE_METADATA_FAILED,
+                         "None of the specified keys is writable");
+    if (callback) {
+      callback (source, media, failed_keys, user_data, error);
+    }
+
+    g_error_free (error);
+    g_list_free (failed_keys);
+    g_hash_table_unref (map);
+
+    return;
+  }
+
+  smrc = g_slice_new (struct StoreMetadataRelayCb);
+  smrc->source = g_object_ref (source);
+  smrc->media = g_object_ref (media);
+  smrc->map = map;
+  smrc->use_sources = g_hash_table_get_keys (map);
+  smrc->failed_keys = failed_keys;
+  smrc->specs = NULL;
+  smrc->user_callback = callback;
+  smrc->user_data = user_data;
+
+  g_idle_add (store_metadata_idle, smrc);
+}
+
+static gboolean
+check_options (GrlSource *source,
+               GrlSupportedOps operation,
+               GrlOperationOptions *options)
+{
+  GrlCaps *caps;
+
+  /* FIXME: that check should be in somewhere in GrlOperationOptions */
+  if (grl_operation_options_get_count (options) == 0)
+    return FALSE;
+
+  caps = grl_source_get_caps (source, operation);
+
+  return grl_operation_options_obey_caps (options, caps, NULL, NULL);
+}
+
+/* ============= API ============= */
+
+/**
+ * grl_source_supported_keys:
+ * @source: a source
+ *
+ * Get a list of #GrlKeyID, which describe a metadata types that this
+ * source can fetch and store.
+ *
+ * Returns: (element-type GrlKeyID) (transfer none): a #GList with the keys
+ */
+const GList *
+grl_source_supported_keys (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  if (GRL_SOURCE_GET_CLASS (source)->supported_keys) {
+    return GRL_SOURCE_GET_CLASS (source)->supported_keys (source);
+  } else {
+    return NULL;
+  }
+}
+
+/**
+ * grl_source_slow_keys:
+ * @source: a source
+ *
+ * Similar to grl_source_supported_keys(), but these keys
+ * are marked as slow because of the amount of traffic/processing needed
+ * to fetch them.
+ *
+ * Returns: (element-type GrlKeyID) (transfer none): a #GList with the keys
+ */
+const GList *
+grl_source_slow_keys (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  if (GRL_SOURCE_GET_CLASS (source)->slow_keys) {
+    return GRL_SOURCE_GET_CLASS (source)->slow_keys (source);
+  } else {
+    return NULL;
+  }
+}
+
+/**
+ * grl_source_writable_keys:
+ * @source: a source
+ *
+ * Similar to grl_source_supported_keys(), but these keys
+ * are marked as writable, meaning the source allows the client
+ * to provide new values for these keys that will be stored permanently.
+ *
+ * Returns: (element-type GrlKeyID) (transfer none):
+ * a #GList with the keys
+ */
+const GList *
+grl_source_writable_keys (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  if (GRL_SOURCE_GET_CLASS (source)->writable_keys) {
+    return GRL_SOURCE_GET_CLASS (source)->writable_keys (source);
+  } else {
+    return NULL;
+  }
+}
+
+/**
+ * grl_source_get_id:
+ * @source: a source
+ *
+ * Returns: the ID of the @source
+ */
+const gchar *
+grl_source_get_id (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  return source->priv->id;
+}
+
+/**
+ * grl_source_get_name:
+ * @source: a source
+ *
+ * Returns: the name of the @source
+ */
+const gchar *
+grl_source_get_name (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  return source->priv->name;
+}
+
+/**
+ * grl_source_get_description:
+ * @source: a source
+ *
+ * Returns: the description of the @source
+ */
+const gchar *
+grl_source_get_description (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  return source->priv->desc;
+}
+
+/**
+ * grl_source_get_plugin:
+ * @source: a source
+ *
+ * Returns: (transfer none): the plugin this source belongs to
+ **/
+GrlPlugin *
+grl_source_get_plugin (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), NULL);
+
+  return source->priv->plugin;
+}
+
+/**
+ * grl_source_get_rank:
+ * @source: a source
+ *
+ * Gets the source rank
+ *
+ * Returns: rank value
+ **/
+gint
+grl_source_get_rank (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+
+  return source->priv->rank;
+}
+
+/**
+ * grl_source_cancel:
+ * @source: a source
+ * @operation_id: the identifier of the running operation, as returned by the
+ * function that started it
+ *
+ * Cancel a running method.
+ *
+ * The derived class must implement the cancel vmethod in order to honour the
+ * request correctly. Otherwise, the operation will not be interrupted.
+ *
+ * In all cases, if this function is called on an ongoing operation, the
+ * corresponding callback will be called with the
+ * @GRL_CORE_ERROR_OPERATION_CANCELLED error set, and no more action will be
+ * taken for that operation after the said callback with error has been called.
+ */
+void
+grl_source_cancel (GrlSource *source, guint operation_id)
+{
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+
+  if (!operation_is_ongoing (operation_id)) {
+    GRL_DEBUG ("Tried to cancel invalid or already cancelled operation. "
+               "Skipping...");
+    return;
+  }
+
+  /* Mark the operation as finished, if the source does not implement
+     cancellation or it did not make it in time, we will not emit the results
+     for this operation in any case.  At any rate, we will not free the
+     operation data until we are sure the plugin won't need it any more. In the
+     case of operations dealing with multiple results, like browse() or
+     search(), this will happen when it emits remaining = 0 (which can be
+     because it did not cancel the op or because it managed to cancel it and is
+     signaling so) */
+  operation_set_cancelled (operation_id);
+
+  /* If the source provides an implementation for operation cancellation,
+     let's use that to avoid further unnecessary processing in the plugin */
+  if (!operation_is_started (operation_id) &&
+      GRL_SOURCE_GET_CLASS (source)->cancel) {
+    GRL_SOURCE_GET_CLASS (source)->cancel (source, operation_id);
+  }
+}
+
+/**
+ * grl_source_supported_operations:
+ * @source: a source
+ *
+ * By default the derived objects of #GrlSource can only resolve.
+ *
+ * Returns: (type uint): a bitwise mangle with the supported operations by
+ * the source
+ */
+GrlSupportedOps
+grl_source_supported_operations (GrlSource *source)
+{
+  GrlSupportedOps ops = GRL_OP_NONE;
+  GrlSourceClass *source_class;
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), GRL_OP_NONE);
+
+  source_class = GRL_SOURCE_GET_CLASS (source);
+
+  if (source_class->supported_operations) {
+    return  source_class->supported_operations (source);
+  }
+
+  if (source_class->resolve) {
+    ops |= GRL_OP_RESOLVE;
+  }
+  if (source_class->test_media_from_uri &&
+      source_class->media_from_uri) {
+    ops |= GRL_OP_MEDIA_FROM_URI;
+  }
+  if (source_class->browse) {
+    ops |= GRL_OP_BROWSE;
+  }
+  if (source_class->search) {
+    ops |= GRL_OP_SEARCH;
+  }
+  if (source_class->query) {
+    ops |= GRL_OP_QUERY;
+  }
+  if (source_class->remove) {
+    ops |= GRL_OP_REMOVE;
+  }
+  if (source_class->store_metadata) {
+    ops |= GRL_OP_STORE_METADATA;
+  }
+
+  if (source_class->notify_change_start &&
+      source_class->notify_change_stop) {
+    ops |= GRL_OP_NOTIFY_CHANGE;
+  }
+
+  return ops;
+}
+
+/**
+ * grl_source_get_auto_split_threshold:
+ * @source: a source
+ *
+ * Gets how much elements the source is able to handle in a single request.
+ *
+ * See #grilo_source_set_auto_split_threshold()
+ *
+ * Returns: the assigned threshold, or 0 if there is no threshold
+ */
+guint
+grl_source_get_auto_split_threshold (GrlSource *source)
+{
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+
+  return source->priv->auto_split_threshold;
+}
+
+/**
+ * grl_source_set_auto_split_threshold:
+ * @source: a source
+ * @threshold: the threshold to set
+ *
+ * Sets how much elements the source is able to handle in a single request.
+ *
+ * If user, during a search or browsing operation, asks for more elements than
+ * the threshold, the request will be automatically splitted in chunks, so up to
+ * @threshold elements will be asked in each request.
+ *
+ * Source will act as if user were asking just a chunk, and user won't notice
+ * that the request was chunked.
+ *
+ * <note>
+ *  <para>
+ *    This function is intended to be used only by plugins.
+ *  </para>
+ * </note>
+ *
+ */
+void
+grl_source_set_auto_split_threshold (GrlSource *source,
+                                     guint threshold)
+{
+  g_return_if_fail (GRL_IS_SOURCE (source));
+
+  source->priv->auto_split_threshold = threshold;
+}
+
+/**
+ * grl_source_resolve:
+ * @source: a source
+ * @media: (allow-none): a data transfer object
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options to pass to this operation
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * This method is intended to fetch the requested keys of metadata of
+ * a given @media to the media source.
+ *
+ * This method is asynchronous.
+ *
+ * Returns: the operation identifier
+ */
+guint
+grl_source_resolve (GrlSource *source,
+                    GrlMedia *media,
+                    const GList *keys,
+                    GrlOperationOptions *options,
+                    GrlSourceResolveCb callback,
+                    gpointer user_data)
+{
+  GList *_keys;
+  GList *each_key;
+  GList *delete_key;
+  struct ResolveRelayCb *rrc;
+  guint operation_id;
+  GList *sources;
+  GrlResolutionFlags flags;
+  GrlOperationOptions *resolve_options;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+  g_return_val_if_fail (keys != NULL, 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_RESOLVE, 0);
+  g_return_val_if_fail (check_options (source, GRL_OP_RESOLVE, options), 0);
+
+  if (!media) {
+    /* Special case, NULL media ==> root container */
+    media = grl_media_box_new ();
+    grl_media_set_id (media, NULL);
+    grl_media_set_source (media, grl_source_get_id (source));
+  } else if (!grl_media_get_source (media)) {
+    grl_media_set_source (media, grl_source_get_id (source));
+  }
+
+  /* By default assume we will use the parameters specified by the user */
+  _keys = filter_known_keys (media, (GList *) keys);
+
+  flags = grl_operation_options_get_flags (options);
+
+  if (flags & GRL_RESOLVE_FULL) {
+    GRL_DEBUG ("requested full metadata");
+    sources = grl_plugin_registry_get_sources_by_operations (grl_plugin_registry_get_default (),
+                                                             GRL_OP_RESOLVE,
+                                                             TRUE);
+    /* Put current source on top */
+    sources = g_list_remove (sources, source);
+    sources = g_list_prepend (sources, source);
+    flags &= ~GRL_RESOLVE_FULL;
+    resolve_options = grl_operation_options_copy (options);
+    grl_operation_options_set_flags (resolve_options, flags);
+  } else {
+    /* Consider only this source */
+    sources = g_list_prepend (NULL, source);
+    resolve_options = g_object_ref (options);
+  }
+
+  if (flags & GRL_RESOLVE_FAST_ONLY) {
+    GRL_DEBUG ("requested fast keys");
+  }
+
+  _keys = filter_unresolvable_keys (sources, &_keys);
+
+  operation_id = grl_operation_generate_id ();
+
+  operation_set_ongoing (source, operation_id);
+
+  /* Always hook an own relay callback so we can do some
+     post-processing before handing out the results
+     to the user */
+  rrc = g_slice_new (struct ResolveRelayCb);
+  rrc->source = g_object_ref (source);
+  rrc->operation_type = GRL_OP_RESOLVE;
+  rrc->operation_id = operation_id;
+  rrc->media = g_object_ref (media);
+  rrc->keys = _keys;
+  rrc->options = resolve_options;
+  rrc->user_callback = callback;
+  rrc->user_data = user_data;
+  rrc->cancel_invoked = FALSE;
+  rrc->map = map_keys_new ();
+  rrc->resolve_specs = map_sources_new ();
+  rrc->error = NULL;
+  rrc->specs_to_invoke = NULL;
+
+  map_keys_to_sources (rrc->map, _keys, sources, media, flags & GRL_RESOLVE_FAST_ONLY);
+  g_list_free (sources);
+
+  each_key = rrc->keys;
+  while (each_key) {
+    if (map_sources_to_specs (rrc->resolve_specs, rrc->map, media,
+                              GRLPOINTER_TO_KEYID (each_key->data),
+                              resolve_options, rrc)) {
+      each_key = g_list_next (each_key);
+    } else {
+      delete_key = each_key;
+      each_key = g_list_next (each_key);
+      rrc->keys = g_list_delete_link (rrc->keys, delete_key);
+    }
+  }
+
+  rrc->specs_to_invoke = g_hash_table_get_values (rrc->resolve_specs);
+  if (rrc->specs_to_invoke) {
+    g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY?
+                     G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                     resolve_idle,
+                     rrc,
+                     NULL);
+  } else {
+    g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY?
+                     G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                     resolve_all_done,
+                     rrc,
+                     NULL);
+  }
+
+  return operation_id;
+}
+
+/**
+ * grl_source_resolve_sync:
+ * @source: a source
+ * @media: (allow-none): a data transfer object
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options to pass to this operation
+ * @error: a #GError, or @NULL
+ *
+ * This method is intended to fetch the requested keys of metadata of
+ * a given @media to the media source.
+ *
+ * This method is synchronous.
+ *
+ * Returns: (transfer full): a filled #GrlMedia
+ */
+GrlMedia *
+grl_source_resolve_sync (GrlSource *source,
+                         GrlMedia *media,
+                         const GList *keys,
+                         GrlOperationOptions *options,
+                         GError **error)
+{
+  GrlDataSync *ds;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_resolve (source,
+                      media,
+                      keys,
+                      options,
+                      resolve_result_async_cb,
+                      ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  g_slice_free (GrlDataSync, ds);
+
+  return media;
+}
+
+/**
+ * grl_source_may_resolve:
+ * @source: a source
+ * @media: a media on which we want more metadata
+ * @key_id: the key corresponding to a metadata we might want
+ * @missing_keys: an optional originally empty list
+ *
+ * Checks whether @key_id may be resolved with @source for @media, so that the
+ * caller can avoid calling grl_source_resolve() if it can be known in
+ * advance it will fail.
+ *
+ * If the resolution is known to be impossible because more keys are needed in
+ * @media, and @missing_keys is not @NULL, it is populated with the list of
+ * GrlKeyID that would be needed.
+ *
+ * This function is synchronous and should not block.
+ *
+ * Returns: @TRUE if there's a possibility that @source resolves @key_id for
+ * @media, @FALSE otherwise.
+ */
+gboolean
+grl_source_may_resolve (GrlSource *source,
+                        GrlMedia *media,
+                        GrlKeyID key_id,
+                        GList **missing_keys)
+{
+  GrlSourceClass *klass;
+  const GList *supported_keys;
+  const gchar *media_source;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
+  g_return_val_if_fail (!missing_keys || !*missing_keys, FALSE);
+
+  klass = GRL_SOURCE_GET_CLASS (source);
+
+  if (klass->may_resolve) {
+    return klass->may_resolve (source, media, key_id, missing_keys);
+  }
+
+  /* Default behaviour is to assume that if source implements resolve, then any
+     supported key for its own content is resolved */
+  if (klass->resolve) {
+    GRL_DEBUG ("Using default may_resolve()");
+    /* We need to know the media source */
+    if (media == NULL ||
+        (media_source = grl_media_get_source (media)) == NULL) {
+      if (missing_keys) {
+        *missing_keys = NULL;
+        *missing_keys =
+          g_list_prepend (*missing_keys,
+                          GRLKEYID_TO_POINTER (GRL_METADATA_KEY_SOURCE));
+      }
+      return FALSE;
+    }
+    /* Content is from different source */
+    if (g_strcmp0 (grl_source_get_id (source), media_source) != 0) {
+      return FALSE;
+    }
+    /* Check if the key is supported */
+    supported_keys = grl_source_supported_keys (source);
+    return (g_list_find ((GList *) supported_keys,
+                         GRLKEYID_TO_POINTER (key_id)) != NULL);
+  } else {
+    GRL_WARNING ("Source %s does not implement may_resolve()",
+                 grl_source_get_id (source));
+    return FALSE;
+  }
+}
+
+/**
+ * grl_source_test_media_from_uri:
+ * @source: a source
+ * @uri: A URI that can be used to identify a media resource
+ *
+ * Tests whether @source can instantiate a #GrlMedia object representing
+ * the media resource exposed at @uri.
+ *
+ * Returns: %TRUE if it can, %FALSE otherwise.
+ *
+ * This method is synchronous.
+ */
+gboolean
+grl_source_test_media_from_uri (GrlSource *source,
+                                const gchar *uri)
+{
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
+  g_return_val_if_fail (uri != NULL, FALSE);
+
+  if (GRL_SOURCE_GET_CLASS (source)->test_media_from_uri) {
+    return GRL_SOURCE_GET_CLASS (source)->test_media_from_uri (source, uri);
+  } else {
+    return FALSE;
+  }
+}
+
+/**
+ * grl_source_get_media_from_uri:
+ * @source: a source
+ * @uri: A URI that can be used to identify a media resource
+ * @keys: A list of keys to resolve
+ * @flags: the resolution mode
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Creates an instance of #GrlMedia representing the media resource
+ * exposed at @uri.
+ *
+ * It is recommended to call grl_source_test_media_from_uri() before invoking
+ * this to check whether the target source can theoretically do the resolution.
+ *
+ * This method is asynchronous.
+ *
+ * Returns: the operation identifier
+ */
+guint
+grl_source_get_media_from_uri (GrlSource *source,
+                               const gchar *uri,
+                               const GList *keys,
+                               GrlOperationOptions *options,
+                               GrlSourceResolveCb callback,
+                               gpointer user_data)
+{
+  GList *_keys;
+  GrlSourceMediaFromUriSpec *mfus;
+  struct ResolveRelayCb *rrc;
+  guint operation_id;
+  GrlResolutionFlags flags;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+  g_return_val_if_fail (uri != NULL, 0);
+  g_return_val_if_fail (keys != NULL, 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_MEDIA_FROM_URI, 0);
+  g_return_val_if_fail (check_options (source, GRL_OP_MEDIA_FROM_URI, options), 0);
+
+  _keys = g_list_copy ((GList *) keys);
+  flags = grl_operation_options_get_flags (options);
+
+  if (flags & GRL_RESOLVE_FAST_ONLY) {
+    filter_slow (source, &_keys, FALSE);
+  }
+
+  if (flags & GRL_RESOLVE_FULL) {
+    _keys = expand_operation_keys (source, NULL, _keys);
+  }
+
+  operation_id = grl_operation_generate_id ();
+
+  /* We cannot prepare for full resolution yet because we don't
+     have a GrlMedia t operate with.
+     TODO: full resolution could be added in the relay calback
+     when we get the GrlMedia object */
+
+  /* Always hook an own relay callback so we can do some
+     post-processing before handing out the results
+     to the user */
+  rrc = g_slice_new (struct ResolveRelayCb);
+  rrc->source = g_object_ref (source);
+  rrc->operation_type = GRL_OP_MEDIA_FROM_URI;
+  rrc->operation_id = operation_id;
+  rrc->keys = _keys;
+  rrc->options = g_object_ref (options);
+  rrc->user_callback = callback;
+  rrc->user_data = user_data;
+
+  mfus = g_new0 (GrlSourceMediaFromUriSpec, 1);
+  mfus->source = g_object_ref (source);
+  mfus->operation_id = operation_id;
+  mfus->uri = g_strdup (uri);
+  mfus->keys = _keys;
+  mfus->options = grl_operation_options_copy (options);
+  mfus->callback = media_from_uri_result_relay_cb;
+  mfus->user_data = rrc;
+
+  /* Save a reference to the operaton spec in the relay-cb's
+     user_data so that we can free the spec there */
+  rrc->spec.mfu = mfus;
+
+  operation_set_ongoing (source, operation_id);
+
+  g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY?
+                   G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                   media_from_uri_idle,
+                   mfus,
+                   NULL);
+
+  return operation_id;
+}
+
+/**
+ * grl_source_get_media_from_uri_sync:
+ * @source: a source
+ * @uri: A URI that can be used to identify a media resource
+ * @keys: A list of keys to resolve
+ * @options: options wanted for that operation
+ * @error: a #GError, or @NULL
+ *
+ * Creates an instance of #GrlMedia representing the media resource
+ * exposed at @uri.
+ *
+ * It is recommended to call grl_source_test_media_from_uri() before
+ * invoking this to check whether the target source can theoretically do the
+ * resolution.
+ *
+ * This method is synchronous.
+ *
+ * Returns: (transfer full): a filled #GrlMedia
+ */
+GrlMedia *
+grl_source_get_media_from_uri_sync (GrlSource *source,
+                                    const gchar *uri,
+                                    const GList *keys,
+                                    GrlOperationOptions *options,
+                                    GError **error)
+{
+  GrlDataSync *ds;
+  GrlMedia *result;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_get_media_from_uri (source,
+                                 uri,
+                                 keys,
+                                 options,
+                                 resolve_result_async_cb,
+                                 ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  result = (GrlMedia *) ds->data;
+  g_slice_free (GrlDataSync, ds);
+
+  return result;
+}
+
+/**
+ * grl_source_browse:
+ * @source: a source
+ * @container: (allow-none): a container of data transfer objects
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Browse from media elements through an available list.
+ *
+ * This method is asynchronous.
+ *
+ * Returns: the operation identifier
+ */
+guint
+grl_source_browse (GrlSource *source,
+                   GrlMedia *container,
+                   const GList *keys,
+                   GrlOperationOptions *options,
+                   GrlSourceResultCb callback,
+                   gpointer user_data)
+{
+  GList *_keys;
+  GrlSourceBrowseSpec *bs;
+  guint operation_id;
+  struct BrowseRelayCb *brc;
+  GrlResolutionFlags flags;
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_BROWSE, 0);
+  g_return_val_if_fail (check_options (source, GRL_OP_BROWSE, options), 0);
+
+  /* By default assume we will use the parameters specified by the user */
+  _keys = g_list_copy ((GList *) keys);
+
+  flags = grl_operation_options_get_flags (options);
+
+  if (flags & GRL_RESOLVE_FAST_ONLY) {
+    GRL_DEBUG ("requested fast keys");
+    filter_slow (source, &_keys, FALSE);
+  }
+
+  /* Setup full resolution mode if requested */
+  if (flags & GRL_RESOLVE_FULL) {
+    GRL_DEBUG ("requested full metadata");
+    _keys = expand_operation_keys (source, NULL, _keys);
+  }
+
+  operation_id = grl_operation_generate_id ();
+
+  /* Always hook an own relay callback so we can do some
+     post-processing before handing out the results
+     to the user */
+  brc = g_slice_new (struct BrowseRelayCb);
+  brc->source = g_object_ref (source);
+  brc->operation_type = GRL_OP_BROWSE;
+  brc->operation_id = operation_id;
+  brc->keys = _keys;
+  brc->options = g_object_ref (options);
+  brc->user_callback = callback;
+  brc->user_data = user_data;
+  brc->queue = NULL;
+  brc->dispatcher_running = FALSE;
+
+  bs = g_new (GrlSourceBrowseSpec, 1);
+  bs->source = g_object_ref (source);
+  bs->operation_id = operation_id;
+  /* _keys is already a copy */
+  bs->keys = _keys;
+  bs->options = grl_operation_options_copy (options);
+  bs->callback = browse_result_relay_cb;
+  bs->user_data = brc;
+
+  if (!container) {
+    /* Special case: NULL container ==> NULL id */
+    bs->container = grl_media_box_new ();
+    grl_media_set_source (bs->container,
+                          grl_source_get_id (source));
+  } else {
+    bs->container = g_object_ref (container);
+  }
+
+  /* Save a reference to the operaton spec in the relay-cb's
+     user_data so that we can free the spec there when we get
+     the last result */
+  brc->spec.browse = bs;
+
+  /* Setup auto-split management if requested */
+  brc->auto_split = auto_split_setup (source, bs->options);
+
+  operation_set_ongoing (source, operation_id);
+
+  g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY? G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                   browse_idle,
+                   bs,
+                   NULL);
+
+  return operation_id;
+}
+
+/**
+ * grl_source_browse_sync:
+ * @source: a source
+ * @container: (allow-none): a container of data transfer objects
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @error: a #GError, or @NULL
+ *
+ * Browse media elements through an available
+ * list.
+ *
+ * This method is synchronous.
+ *
+ * Returns: (element-type Grl.Media) (transfer full): a #GList with #GrlMedia
+ * elements. After use g_object_unref() every element and g_list_free() the
+ * list.
+ */
+GList *
+grl_source_browse_sync (GrlSource *source,
+                        GrlMedia *container,
+                        const GList *keys,
+                        GrlOperationOptions *options,
+                        GError **error)
+{
+  GrlDataSync *ds;
+  GList *result;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_browse (source,
+                     container,
+                     keys,
+                     options,
+                     multiple_result_async_cb,
+                     ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  result = (GList *) ds->data;
+  g_slice_free (GrlDataSync, ds);
+
+  return result;
+}
+
+/**
+ * grl_source_search:
+ * @source: a source
+ * @text: the text to search
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Search for the @text string in a source for data identified with that string.
+ *
+ * If @text is @NULL then no text filter will be applied, and thus, no media
+ * items from @source will be filtered. If @source does not support NULL-text
+ * search operations it should notiy the client by setting
+ * @GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED in @callback's error parameter.
+ *
+ * This method is asynchronous.
+ *
+ * Returns: the operation identifier
+ */
+guint
+grl_source_search (GrlSource *source,
+                   const gchar *text,
+                   const GList *keys,
+                   GrlOperationOptions *options,
+                   GrlSourceResultCb callback,
+                   gpointer user_data)
+{
+  GList *_keys;
+  GrlSourceSearchSpec *ss;
+  guint operation_id;
+  struct BrowseRelayCb *brc;
+  GrlResolutionFlags flags;
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_SEARCH, 0);
+  g_return_val_if_fail (check_options (source, GRL_OP_SEARCH, options), 0);
+
+  /* By default assume we will use the parameters specified by the user */
+  _keys = g_list_copy ((GList *) keys);
+
+  flags = grl_operation_options_get_flags (options);
+
+  if (flags & GRL_RESOLVE_FAST_ONLY) {
+    GRL_DEBUG ("requested fast keys");
+    filter_slow (source, &_keys, FALSE);
+  }
+
+  /* Setup full resolution mode if requested */
+  if (flags & GRL_RESOLVE_FULL) {
+    GRL_DEBUG ("requested full metadata");
+    _keys = expand_operation_keys (source, NULL, _keys);
+  }
+
+  operation_id = grl_operation_generate_id ();
+
+  /* Always hook an own relay callback so we can do some
+     post-processing before handing out the results
+     to the user */
+  brc = g_slice_new (struct BrowseRelayCb);
+  brc->source = g_object_ref (source);
+  brc->operation_type = GRL_OP_SEARCH;
+  brc->operation_id = operation_id;
+  brc->keys = _keys;
+  brc->options = g_object_ref (options);
+  brc->user_callback = callback;
+  brc->user_data = user_data;
+  brc->queue = NULL;
+  brc->dispatcher_running = FALSE;
+
+  ss = g_new (GrlSourceSearchSpec, 1);
+  ss->source = g_object_ref (source);
+  ss->operation_id = operation_id;
+  ss->text = g_strdup (text);
+  ss->keys = _keys;
+  ss->options = grl_operation_options_copy (options);
+  ss->callback = browse_result_relay_cb;
+  ss->user_data = brc;
+
+  /* Save a reference to the operaton spec in the relay-cb's
+     user_data so that we can free the spec there when we get
+     the last result */
+  brc->spec.search = ss;
+
+  /* Setup auto-split management if requested */
+  brc->auto_split = auto_split_setup (source, ss->options);
+
+  operation_set_ongoing (source, operation_id);
+
+  g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY? G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                   search_idle,
+                   ss,
+                   NULL);
+
+  return operation_id;
+}
+
+/**
+ * grl_source_search_sync:
+ * @source: a source
+ * @text: the text to search
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @error: a #GError, or @NULL
+ *
+ * Search for the @text string in a source for data identified with that string.
+ *
+ * If @text is @NULL then no text filter will be applied, and thus, no media
+ * items from @source will be filtered. If @source does not support NULL-text
+ * search operations it should notiy the client by setting
+ * @GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED in the error parameter.
+ *
+ * This method is synchronous.
+ *
+ * Returns: (element-type Grl.Media) (transfer full): a #GList with #GrlMedia
+ * elements. After use g_object_unref() every element and g_list_free() the
+ * list.
+ */
+GList *
+grl_source_search_sync (GrlSource *source,
+                        const gchar *text,
+                        const GList *keys,
+                        GrlOperationOptions *options,
+                        GError **error)
+{
+  GrlDataSync *ds;
+  GList *result;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_search (source,
+                     text,
+                     keys,
+                     options,
+                     multiple_result_async_cb,
+                     ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  result = (GList *) ds->data;
+  g_slice_free (GrlDataSync, ds);
+
+  return result;
+}
+
+/**
+ * grl_source_query:
+ * @source: a source
+ * @query: the query to process
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Execute a specialized query (specific for each provider) on a media
+ * repository.
+ *
+ * It is different from grl_source_search() semantically, because the query
+ * implies a carefully crafted string, rather than a simple string to search.
+ *
+ * This method is asynchronous.
+ *
+ * Returns: the operation identifier
+ */
+guint
+grl_source_query (GrlSource *source,
+                  const gchar *query,
+                  const GList *keys,
+                  GrlOperationOptions *options,
+                  GrlSourceResultCb callback,
+                  gpointer user_data)
+{
+  GList *_keys;
+  GrlSourceQuerySpec *qs;
+  guint operation_id;
+  struct BrowseRelayCb *brc;
+  GrlResolutionFlags flags;
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), 0);
+  g_return_val_if_fail (query != NULL, 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_QUERY, 0);
+  g_return_val_if_fail (check_options (source, GRL_OP_QUERY, options), 0);
+
+  /* By default assume we will use the parameters specified by the user */
+  _keys = g_list_copy ((GList *) keys);
+
+  flags = grl_operation_options_get_flags (options);
+
+  if (flags & GRL_RESOLVE_FAST_ONLY) {
+    GRL_DEBUG ("requested fast keys");
+    filter_slow (source, &_keys, FALSE);
+  }
+
+  if (flags & GRL_RESOLVE_FULL) {
+    GRL_DEBUG ("requested full metadata");
+    _keys = expand_operation_keys (source, NULL, _keys);
+  }
+
+  operation_id = grl_operation_generate_id ();
+
+  /* Always hook an own relay callback so we can do some
+     post-processing before handing out the results
+     to the user */
+  brc = g_slice_new (struct BrowseRelayCb);
+  brc->source = g_object_ref (source);
+  brc->operation_type = GRL_OP_QUERY;
+  brc->operation_id = operation_id;
+  brc->keys = _keys;
+  brc->options = g_object_ref (options);
+  brc->user_callback = callback;
+  brc->user_data = user_data;
+  brc->queue = NULL;
+  brc->dispatcher_running = FALSE;
+
+  qs = g_new (GrlSourceQuerySpec, 1);
+  qs->source = g_object_ref (source);
+  qs->operation_id = operation_id;
+  qs->query = g_strdup (query);
+  qs->keys = _keys;
+  qs->options = grl_operation_options_copy (options);
+  qs->callback = browse_result_relay_cb;
+  qs->user_data = brc;
+
+  /* Save a reference to the operaton spec in the relay-cb's
+     user_data so that we can free the spec there when we get
+     the last result */
+  brc->spec.query = qs;
+
+  /* Setup auto-split management if requested */
+  brc->auto_split = auto_split_setup (source, qs->options);
+
+  operation_set_ongoing (source, operation_id);
+
+  g_idle_add_full (flags & GRL_RESOLVE_IDLE_RELAY? G_PRIORITY_DEFAULT_IDLE: G_PRIORITY_HIGH_IDLE,
+                   query_idle,
+                   qs,
+                   NULL);
+
+  return operation_id;
+}
+
+/**
+ * grl_source_query_sync:
+ * @source: a source
+ * @query: the query to process
+ * @keys: (element-type GrlKeyID): the #GList of
+ * #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @error: a #GError, or @NULL
+ *
+ * Execute a specialized query (specific for each provider) on a media
+ * repository.
+ *
+ * This method is synchronous.
+ *
+ * Returns: (element-type Grl.Media) (transfer full): a #GList with #GrlMedia
+ * elements. After use g_object_unref() every element and g_list_free() the
+ * list.
+ */
+GList *
+grl_source_query_sync (GrlSource *source,
+                       const gchar *query,
+                       const GList *keys,
+                       GrlOperationOptions *options,
+                       GError **error)
+{
+  GrlDataSync *ds;
+  GList *result;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_query (source,
+                    query,
+                    keys,
+                    options,
+                    multiple_result_async_cb,
+                    ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  result = (GList *) ds->data;
+  g_slice_free (GrlDataSync, ds);
+
+  return result;
+}
+
+/**
+ * grl_source_remove:
+ * @source: a source
+ * @media: a data transfer object
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Remove a @media from the @source repository.
+ *
+ * This method is asynchronous.
+ */
+void
+grl_source_remove (GrlSource *source,
+                   GrlMedia *media,
+                   GrlSourceRemoveCb callback,
+                   gpointer user_data)
+{
+  const gchar *id;
+  struct RemoveRelayCb *rrc;
+  GrlSourceRemoveSpec *rs;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+  g_return_if_fail (GRL_IS_MEDIA (media));
+  g_return_if_fail (callback != NULL);
+  g_return_if_fail (grl_source_supported_operations (source) &
+                    GRL_OP_REMOVE);
+
+  rrc = g_slice_new (struct RemoveRelayCb);
+  rrc->source = g_object_ref (source);
+  rrc->media = g_object_ref (media);
+  rrc->user_callback = callback;
+  rrc->user_data = user_data;
+
+  /* Check that we have the minimum information we need */
+  id = grl_media_get_id (media);
+  if (!id) {
+    rrc->error = g_error_new (GRL_CORE_ERROR,
+                              GRL_CORE_ERROR_REMOVE_FAILED,
+                              "Media has no id, cannot remove");
+    rrc->spec = NULL;
+  } else {
+    rrc->error = NULL;
+    rs = g_new0 (GrlSourceRemoveSpec, 1);
+    rs->source = g_object_ref (source);
+    rs->media_id = g_strdup (id);
+    rs->media = g_object_ref (media);
+    rs->callback = remove_result_relay_cb;
+    rs->user_data = rrc;
+    rrc->spec = rs;
+  }
+
+  g_idle_add (remove_idle, rrc);
+}
+
+/**
+ * grl_source_remove_sync:
+ * @source: a source
+ * @media: a data transfer object
+ * @error: a #GError, or @NULL
+ *
+ * Remove a @media from the @source repository.
+ *
+ * This method is synchronous.
+ *
+ * Since: 0.1.6
+ */
+void
+grl_source_remove_sync (GrlSource *source,
+                        GrlMedia *media,
+                        GError **error)
+{
+  GrlDataSync *ds;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_remove (source,
+                     media,
+                     remove_async_cb,
+                     ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  g_slice_free (GrlDataSync, ds);
+}
+
+/**
+ * grl_source_store:
+ * @source: a source
+ * @parent: (allow-none): a parent to store the data transfer objects
+ * @media: a data transfer object
+ * @flags: flags to configure specific behaviour of the operation
+ * @callback: (scope notified): the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Store the @media into the @parent container
+ *
+ * This method is asynchronous.
+ */
+void
+grl_source_store (GrlSource *source,
+                  GrlMediaBox *parent,
+                  GrlMedia *media,
+                  GrlWriteFlags flags,
+                  GrlSourceStoreCb callback,
+                  gpointer user_data)
+{
+  struct StoreRelayCb *src;
+  GrlSourceStoreSpec *ss;
+
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+  g_return_if_fail (!parent || GRL_IS_MEDIA_BOX (parent));
+  g_return_if_fail (GRL_IS_MEDIA (media));
+
+  g_return_if_fail ((!parent &&
+                     grl_source_supported_operations (source) & GRL_OP_STORE) ||
+                    (parent &&
+                     grl_source_supported_operations (source) & GRL_OP_STORE_PARENT));
+
+  src = g_slice_new (struct StoreRelayCb);
+  src->flags = flags;
+  src->user_callback = callback;
+  src->user_data = user_data;
+
+  ss = g_new (GrlSourceStoreSpec, 1);
+  ss->source = g_object_ref (source);
+  ss->parent = parent? g_object_ref (parent): NULL;
+  ss->media = g_object_ref (media);
+  ss->callback = store_relay_cb;
+  ss->user_data = src;
+
+  g_idle_add (store_idle, ss);
+}
+
+/**
+ * grl_source_store_sync:
+ * @source: a source
+ * @parent: (allow-none): a #GrlMediaBox to store the data transfer objects
+ * @media: a #GrlMedia data transfer object
+ * @error: a #GError, or @NULL
+ *
+ * Store the @media into the @parent container.
+ *
+ * This method is synchronous.
+ */
+void
+grl_source_store_sync (GrlSource *source,
+                       GrlMediaBox *parent,
+                       GrlMedia *media,
+                       GrlWriteFlags flags,
+                       GError **error)
+{
+  GrlDataSync *ds;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_store (source,
+                    parent,
+                    media,
+                    flags,
+                    store_result_async_cb,
+                    ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  g_slice_free (GrlDataSync, ds);
+}
+
+/**
+ * grl_source_store_metadata:
+ * @source: a metadata source
+ * @media: the #GrlMedia object that we want to operate on.
+ * @keys: (element-type GObject.ParamSpec) (allow-none): a list
+ * of #GrlKeyID whose values we want to change.
+ * @flags: Flags to configure specific behaviors of the operation.
+ * @callback: (scope notified): the callback to execute when the operation is finished.
+ * @user_data: user data set for the @callback
+ *
+ * This is the main method of the #GrlMetadataSource class. It will
+ * get the values for @keys from @media and store it permanently. After
+ * calling this method, future queries that return this media object
+ * shall return this new values for the selected keys.
+ *
+ * This function is asynchronous and uses the Glib's main loop.
+ */
+void
+grl_source_store_metadata (GrlSource *source,
+                           GrlMedia *media,
+                           GList *keys,
+                           GrlWriteFlags flags,
+                           GrlSourceStoreCb callback,
+                           gpointer user_data)
+{
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+  g_return_if_fail (GRL_IS_MEDIA (media));
+  g_return_if_fail (keys != NULL);
+  g_return_if_fail (grl_source_supported_operations (source) &
+                    GRL_OP_STORE_METADATA);
+
+  run_store_metadata (source, media, keys, flags, callback, user_data);
+}
+
+/**
+ * grl_source_store_metadata_sync:
+ * @source: a source
+ * @media: the #GrlMedia object that we want to operate on
+ * @keys: (element-type GrlKeyID) (allow-none): a list of
+ * #GrlKeyID whose values we want to change
+ * @flags: Flags to configure specific behaviors of the operation.
+ * @error: a #GError, or @NULL
+ *
+ * Update @keys values from @media in the @source. After calling this method,
+ * future queries that return this media object shall return this new value for
+ * the selected key.
+ *
+ * This function is synchronous.
+ *
+ * Returns: (element-type GrlKeyID) (transfer container):
+ * a #GList of keys that could not be updated, or @NULL
+ */
+GList *
+grl_source_store_metadata_sync (GrlSource *source,
+                                GrlMedia *media,
+                                GList *keys,
+                                GrlWriteFlags flags,
+                                GError **error)
+{
+  GrlDataSync *ds;
+  GList *failed;
+
+  ds = g_slice_new0 (GrlDataSync);
+
+  grl_source_store_metadata (source,
+                             media,
+                             keys,
+                             flags,
+                             store_metadata_result_async_cb,
+                             ds);
+
+  grl_wait_for_async_operation_complete (ds);
+
+  if (ds->error) {
+    if (error) {
+      *error = ds->error;
+    } else {
+      g_error_free (ds->error);
+    }
+  }
+
+  failed = ds->data;
+
+  g_slice_free (GrlDataSync, ds);
+
+  return failed;
+}
+
+/**
+ * grl_source_notify_change_start:
+ * @source: a source
+ * @error: a #GError, or @NULL
+ *
+ * Starts emitting ::content-changed signals when @source discovers changes in
+ * the content. This instructs @source to setup the machinery needed to be aware
+ * of changes in the content.
+ *
+ * Returns: @TRUE if initialization has succeed.
+ */
+gboolean
+grl_source_notify_change_start (GrlSource *source,
+                                GError **error)
+{
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_NOTIFY_CHANGE, FALSE);
+
+  return GRL_SOURCE_GET_CLASS (source)->notify_change_start (source, error);
+}
+
+/**
+ * grl_source_notify_change_stop:
+ * @source: a source
+ * @error: a #GError, or @NULL
+ *
+ * This will drop emission of ::content-changed signals from @source. When this
+ * is done @source should stop the machinery required for it to track changes in
+ * the content.
+ *
+ * Returns: @TRUE if stop has succeed.
+ */
+gboolean
+grl_source_notify_change_stop (GrlSource *source,
+                               GError **error)
+{
+  GRL_DEBUG (__FUNCTION__);
+
+  g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
+  g_return_val_if_fail (grl_source_supported_operations (source) &
+                        GRL_OP_NOTIFY_CHANGE, FALSE);
+
+  return GRL_SOURCE_GET_CLASS (source)->notify_change_stop (source, error);
+}
+
+/**
+ * grl_source_notify_change_list:
+ * @source: a source
+ * @changed_medias: (element-type Grl.Media) (transfer full):: the list of
+ * medias that have changed
+ * @change_type: the type of change
+ * @location_unknown: if change has happpened in @media or any descendant
+ *
+ * Emits "content-changed" signal to notify subscribers that a change ocurred
+ * in @source.
+ *
+ * The function will take ownership of @changed medias and it should not be
+ * manipulated in any way by the caller after invoking this function. If that is
+ * needed, the caller must ref the array in advance.
+ *
+ * See GrlSource::content-changed signal.
+ *
+ * <note>
+ *  <para>
+ *    This function is intended to be used only by plugins.
+ *  </para>
+ * </note>
+ */
+void grl_source_notify_change_list (GrlSource *source,
+                                    GPtrArray *changed_medias,
+                                    GrlSourceChangeType change_type,
+                                    gboolean location_unknown)
+{
+  const gchar *source_id;
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+  g_return_if_fail (changed_medias);
+
+  /* Set the source */
+  source_id = grl_source_get_id (source);
+  g_ptr_array_foreach (changed_medias,
+                       (GFunc) grl_media_set_source,
+                       (gpointer) source_id);
+
+  /* Add hook to free content when freeing the array */
+  g_ptr_array_set_free_func (changed_medias, (GDestroyNotify) g_object_unref);
+
+  g_signal_emit (source,
+                 registry_signals[SIG_CONTENT_CHANGED],
+                 0,
+                 changed_medias,
+                 change_type,
+                 location_unknown);
+
+  g_ptr_array_unref (changed_medias);
+}
+
+/**
+ * grl_source_notify_change:
+ * @source: a source
+ * @media: (allow-none): the media which has changed, or @NULL to use the root box.
+ * @change_type: the type of change
+ * @location_unknown: if change has happened in @media or any descendant
+ *
+ * Emits "content-changed" signal to notify subscribers that a change ocurred
+ * in @source.
+ *
+ * See #grl_source_notify_change_list() function.
+ *
+ * <note>
+ *  <para>
+ *    This function is intended to be used only by plugins.
+ *  </para>
+ * </note>
+ */
+void grl_source_notify_change (GrlSource *source,
+                               GrlMedia *media,
+                               GrlSourceChangeType change_type,
+                               gboolean location_unknown)
+{
+  GPtrArray *ptr_array;
+
+  g_return_if_fail (GRL_IS_SOURCE (source));
+
+  if (!media) {
+    media = grl_media_box_new ();
+  } else {
+    g_object_ref (media);
+  }
+
+  ptr_array = g_ptr_array_sized_new (1);
+  g_ptr_array_add (ptr_array, media);
+  grl_source_notify_change_list (source, ptr_array,
+                                 change_type, location_unknown);
+}
+
+/******************************************************************************/
+
 /**
  * grl_source_get_caps:
  * @source: a source
diff --git a/src/grl-source.h b/src/grl-source.h
index e317e8e..c73c21b 100644
--- a/src/grl-source.h
+++ b/src/grl-source.h
@@ -29,6 +29,7 @@
 
 #include <grl-metadata-key.h>
 #include <grl-media.h>
+#include <grl-media-box.h>
 #include <grl-definitions.h>
 #include <grl-plugin.h>
 #include <grl-operation-options.h>
@@ -80,15 +81,14 @@ struct _GrlSource {
 /**
  * GrlSupportedOps:
  * @GRL_OP_NONE: no operation is supported
- * @GRL_OP_METADATA: Fetch specific keys of metadata based on the media id.
  * @GRL_OP_RESOLVE: Fetch specific keys of metadata based on other metadata.
  * @GRL_OP_BROWSE: Retrieve complete sets of #GrlMedia
  * @GRL_OP_SEARCH: Look up for #GrlMedia given a search text
  * @GRL_OP_QUERY:  Look up for #GrlMedia give a service specific query
  * @GRL_OP_STORE: Store content in a service
  * @GRL_OP_STORE_PARENT: Store content as child of a certian parent category.
+ * @GRL_OP_STORE_METADATA: Update metadata of a #GrlMedia in a service.
  * @GRL_OP_REMOVE: Remove content from a service.
- * @GRL_OP_SET_METADATA: Update metadata of a #GrlMedia in a service.
  * @GRL_OP_MEDIA_FROM_URI: Create a #GrlMedia instance from an URI
  * representing a media resource.
  * @GRL_OP_NOTIFY_CHANGE: Notify about changes in the #GrlMediaSource.
@@ -98,19 +98,300 @@ struct _GrlSource {
  */
 typedef enum {
   GRL_OP_NONE            = 0,
-  GRL_OP_METADATA        = 1,
-  GRL_OP_RESOLVE         = 1 << 1,
-  GRL_OP_BROWSE          = 1 << 2,
-  GRL_OP_SEARCH          = 1 << 3,
-  GRL_OP_QUERY           = 1 << 4,
-  GRL_OP_STORE           = 1 << 5,
-  GRL_OP_STORE_PARENT    = 1 << 6,
+  GRL_OP_RESOLVE         = 1,
+  GRL_OP_BROWSE          = 1 << 1,
+  GRL_OP_SEARCH          = 1 << 2,
+  GRL_OP_QUERY           = 1 << 3,
+  GRL_OP_STORE           = 1 << 4,
+  GRL_OP_STORE_PARENT    = 1 << 5,
+  GRL_OP_STORE_METADATA  = 1 << 6,
   GRL_OP_REMOVE          = 1 << 7,
-  GRL_OP_SET_METADATA    = 1 << 8,
-  GRL_OP_MEDIA_FROM_URI  = 1 << 9,
-  GRL_OP_NOTIFY_CHANGE   = 1 << 10,
+  GRL_OP_MEDIA_FROM_URI  = 1 << 8,
+  GRL_OP_NOTIFY_CHANGE   = 1 << 9
 } GrlSupportedOps;
 
+/**
+ * GrlSourceChangeType:
+ * @GRL_CONTENT_CHANGED: content has changed. It is used when any property of
+ * #GrlMedia has changed, or in case of #GrlMediaBox, if several children have
+ * been added and removed.
+ * @GRL_CONTENT_ADDED: new content has been added.
+ * @GRL_CONTENT_REMOVED: content has been removed
+ *
+ * Specifies which kind of change has happened in the plugin
+ */
+typedef enum {
+  GRL_CONTENT_CHANGED,
+  GRL_CONTENT_ADDED,
+  GRL_CONTENT_REMOVED
+} GrlSourceChangeType;
+
+/**
+ * GrlSourceResolveCb:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @media: (transfer full): a data transfer object
+ * @user_data: user data passed to grl_source_resolve()
+ * @error: (type uint): possible #GError generated at processing
+ *
+ * Prototype for the callback passed to grl_source_resolve()
+ */
+typedef void (*GrlSourceResolveCb) (GrlSource *source,
+                                    guint operation_id,
+                                    GrlMedia *media,
+                                    gpointer user_data,
+                                    const GError *error);
+
+/**
+ * GrlSourceResultCb:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @media: (transfer full): a data transfer object
+ * @remaining: the number of remaining #GrlMedia to process, or
+ * GRL_SOURCE_REMAINING_UNKNOWN if it is unknown
+ * @user_data: user data passed to the used method
+ * @error: (type uint): possible #GError generated at processing
+ *
+ * Prototype for the callback passed to the media sources' methods
+ */
+typedef void (*GrlSourceResultCb) (GrlSource *source,
+                                   guint operation_id,
+                                   GrlMedia *media,
+                                   guint remaining,
+                                   gpointer user_data,
+                                   const GError *error);
+
+/**
+ * GrlSourceRemoveCb:
+ * @source: a source
+ * @media: (transfer full): a data transfer object
+ * @user_data: user data passed to grl_source_remove()
+ * @error: (type uint): possible #GError generated at processing
+ *
+ * Prototype for the callback passed to grl_source_remove()
+ */
+typedef void (*GrlSourceRemoveCb) (GrlSource *source,
+                                   GrlMedia *media,
+                                   gpointer user_data,
+                                   const GError *error);
+
+/**
+ * GrlSourceStoreCb:
+ * @source: a source
+ * @media: (transfer full): a #GrlMedia transfer object
+ * @failed_keys: (element-type GrlKeyID) (transfer container): #GList of
+ * keys that could not be updated, if any
+ * @user_data: user data
+ * @error: (type uint): possible #GError generated
+ *
+ * Prototype for the callback passed to grl_source_store_foo functions
+ */
+typedef void (*GrlSourceStoreCb) (GrlSource *source,
+                                  GrlMedia *media,
+                                  GList *failed_keys,
+                                  gpointer user_data,
+                                  const GError *error);
+
+/**
+ * GrlSourceResolveSpec:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @media: a data transfer object
+ * @keys: the #GList of #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * resolve vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  guint operation_id;
+  GrlMedia *media;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResolveCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceResolveSpec;
+
+/**
+ * GrlSourceMediaFromUriSpec:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @uri: A URI that can be used to identify a media resource
+ * @keys: Metadata keys to resolve
+ * @options: options wanted for that operation
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * media_from_uri vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  guint operation_id;
+  gchar *uri;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResolveCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceMediaFromUriSpec;
+
+/**
+ * GrlSourceBrowseSpec:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @container: a container of data transfer objects
+ * @keys: the #GList of #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * browse vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  guint operation_id;
+  GrlMedia *container;
+  GList *keys;
+  GrlOperationOptions *options;
+    GrlSourceResultCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceBrowseSpec;
+
+/**
+ * GrlSourceSearchSpec:
+ * @source: a source
+ * @operation_id: operation identifier
+ * @text: the text to search
+ * @keys: the #GList of #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * search vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  guint operation_id;
+  gchar *text;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResultCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceSearchSpec;
+
+/**
+ * GrlSourceQuerySpec:
+ * @source: a source
+ * @query_id: operation identifier
+ * @query: the query to process
+ * @keys: the #GList of #GrlKeyID<!-- -->s to request
+ * @options: options wanted for that operation
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * query vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  guint operation_id;
+  gchar *query;
+  GList *keys;
+  GrlOperationOptions *options;
+  GrlSourceResultCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceQuerySpec;
+
+/**
+ * GrlSourceRemoveSpec:
+ * @source: a source
+ * @media_id: media identifier to remove
+ * @media: a data transfer object
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * store vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  gchar *media_id;
+  GrlMedia *media;
+  GrlSourceRemoveCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceRemoveSpec;
+
+/**
+ * GrlSourceStoreSpec:
+ * @source: a media source
+ * @parent: a parent to store the data transfer objects
+ * @media: a data transfer object
+ * @callback: the user defined callback
+ * @user_data: the user data to pass in the callback
+ *
+ * Data transport structure used internally by the plugins which support
+ * store vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  GrlMediaBox *parent;
+  GrlMedia *media;
+  GrlSourceStoreCb callback;
+  gpointer user_data;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceStoreSpec;
+
+/**
+ * GrlSourceStoreMetadataSpec:
+ * @source: a source
+ * @media: a #GrlMedia transfer object
+ * @keys: List of keys to be stored/updated.
+ * @flags: Flags to control specific bahviors of the set metadata operation.
+ * @callback: the callback passed to grl_source_store_metadata()
+ * @user_data: user data passed to grl_source_store_metadata()
+ * @failed_keys: for internal use of the framework only.
+ *
+ * Data transport structure used internally by the plugins which support
+ * store_metadata vmethod.
+ */
+typedef struct {
+  GrlSource *source;
+  GrlMedia *media;
+  GList *keys;
+  GrlWriteFlags flags;
+  GrlSourceStoreCb callback;
+  gpointer user_data;
+  GList *failed_keys;
+
+  /*< private >*/
+  gpointer _grl_reserved[GRL_PADDING];
+} GrlSourceStoreMetadataSpec;
+
 /* GrlSource class */
 
 typedef struct _GrlSourceClass GrlSourceClass;
@@ -123,8 +404,20 @@ typedef struct _GrlSourceClass GrlSourceClass;
  * @slow_keys: the list of slow keys that can be fetched
  * @writable_keys: the list of keys which value can be written
  * @get_caps: the capabilities that @source supports for @operation
+ * @resolve: resolve the metadata of a given transfer object
+ * @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.
+ * @browse: browse through a list of media
+ * @search: search for media
+ * @query: query for a specific media
+ * @store: store a media in a container
+ * @store_metadata: update metadata values for a given object in a
+ * permanent fashion
  * @cancel: cancel the current operation
-
+ * @notify_change_start: start emitting signals about changes in content
+ * @notify_change_stop: stop emitting signals about changes in content
+ *
  * Grilo Source class. Override the vmethods to implement the
  * element functionality.
  */
@@ -142,8 +435,37 @@ struct _GrlSourceClass {
 
   GrlCaps * (*get_caps) (GrlSource *source, GrlSupportedOps operation);
 
+  void (*resolve) (GrlSource *source, GrlSourceResolveSpec *ms);
+
+  gboolean (*may_resolve) (GrlSource *source, GrlMedia *media,
+                           GrlKeyID key_id, GList **missing_keys);
+
+  gboolean (*test_media_from_uri) (GrlSource *source,
+                                   const gchar *uri);
+
+  void (*media_from_uri) (GrlSource *source,
+                          GrlSourceMediaFromUriSpec *mfus);
+
+  void (*browse) (GrlSource *source, GrlSourceBrowseSpec *bs);
+
+  void (*search) (GrlSource *source, GrlSourceSearchSpec *ss);
+
+  void (*query) (GrlSource *source, GrlSourceQuerySpec *qs);
+
+  void (*remove) (GrlSource *source, GrlSourceRemoveSpec *ss);
+
+  void (*store) (GrlSource *source, GrlSourceStoreSpec *ss);
+
+  void (*store_metadata) (GrlSource *source, GrlSourceStoreMetadataSpec *sms);
+
   void (*cancel) (GrlSource *source, guint operation_id);
 
+  gboolean (*notify_change_start) (GrlSource *source,
+                                    GError **error);
+
+  gboolean (*notify_change_stop) (GrlSource *source,
+                                  GError **error);
+
   /*< private >*/
   gpointer _grl_reserved[GRL_PADDING];
 };
@@ -163,6 +485,138 @@ const GList *grl_source_writable_keys (GrlSource *source);
 GrlCaps *grl_source_get_caps (GrlSource *source,
                               GrlSupportedOps operation);
 
+void grl_source_set_auto_split_threshold (GrlSource *source,
+                                          guint threshold);
+
+guint grl_source_get_auto_split_threshold (GrlSource *source);
+
+
+guint grl_source_resolve (GrlSource *source,
+                          GrlMedia *media,
+                          const GList *keys,
+                          GrlOperationOptions *options,
+                          GrlSourceResolveCb callback,
+                          gpointer user_data);
+
+GrlMedia *grl_source_resolve_sync (GrlSource *source,
+                                   GrlMedia *media,
+                                   const GList *keys,
+                                   GrlOperationOptions *options,
+                                   GError **error);
+
+gboolean grl_source_may_resolve (GrlSource *source,
+                                 GrlMedia *media,
+                                 GrlKeyID key_id,
+                                 GList **missing_keys);
+
+gboolean grl_source_test_media_from_uri (GrlSource *source,
+                                         const gchar *uri);
+
+guint grl_source_get_media_from_uri (GrlSource *source,
+                                     const gchar *uri,
+                                     const GList *keys,
+                                     GrlOperationOptions *options,
+                                     GrlSourceResolveCb callback,
+                                     gpointer user_data);
+
+GrlMedia *grl_source_get_media_from_uri_sync (GrlSource *source,
+                                              const gchar *uri,
+                                              const GList *keys,
+                                              GrlOperationOptions *options,
+                                              GError **error);
+
+guint grl_source_browse (GrlSource *source,
+                         GrlMedia *container,
+                         const GList *keys,
+                         GrlOperationOptions *options,
+                         GrlSourceResultCb callback,
+                         gpointer user_data);
+
+GList *grl_source_browse_sync (GrlSource *source,
+                               GrlMedia *container,
+                               const GList *keys,
+                               GrlOperationOptions *options,
+                               GError **error);
+
+guint grl_source_search (GrlSource *source,
+                         const gchar *text,
+                         const GList *keys,
+                         GrlOperationOptions *options,
+                         GrlSourceResultCb callback,
+                         gpointer user_data);
+
+GList *grl_source_search_sync (GrlSource *source,
+                               const gchar *text,
+                               const GList *keys,
+                               GrlOperationOptions *options,
+                               GError **error);
+
+guint grl_source_query (GrlSource *source,
+                        const gchar *query,
+                        const GList *keys,
+                        GrlOperationOptions *options,
+                        GrlSourceResultCb callback,
+                        gpointer user_data);
+
+GList *grl_source_query_sync (GrlSource *source,
+                              const gchar *query,
+                              const GList *keys,
+                              GrlOperationOptions *options,
+                              GError **error);
+
+void grl_source_remove (GrlSource *source,
+                        GrlMedia *media,
+                        GrlSourceRemoveCb callback,
+                        gpointer user_data);
+
+void grl_source_remove_sync (GrlSource *source,
+                             GrlMedia *media,
+                             GError **error);
+
+void grl_source_store (GrlSource *source,
+                       GrlMediaBox *parent,
+                       GrlMedia *media,
+                       GrlWriteFlags flags,
+                       GrlSourceStoreCb callback,
+                       gpointer user_data);
+
+void grl_source_store_sync (GrlSource *source,
+                            GrlMediaBox *parent,
+                            GrlMedia *media,
+                            GrlWriteFlags flags,
+                            GError **error);
+
+void grl_source_store_metadata (GrlSource *source,
+                                GrlMedia *media,
+                                GList *keys,
+                                GrlWriteFlags flags,
+                                GrlSourceStoreCb callback,
+                                gpointer user_data);
+
+GList *grl_source_store_metadata_sync (GrlSource *source,
+                                       GrlMedia *media,
+                                       GList *keys,
+                                       GrlWriteFlags flags,
+                                       GError **error);
+
+gboolean grl_source_notify_change_start (GrlSource *source,
+                                         GError **error);
+
+gboolean grl_source_notify_change_stop (GrlSource *source,
+                                        GError **error);
+
+void grl_source_notify_change_list (GrlSource *source,
+                                    GPtrArray *changed_medias,
+                                    GrlSourceChangeType change_type,
+                                    gboolean location_unknown);
+
+void grl_source_notify_change (GrlSource *source,
+                               GrlMedia *media,
+                               GrlSourceChangeType change_type,
+                               gboolean location_unknown);
+
+void grl_source_cancel (GrlSource *source, guint operation_id);
+
 const gchar *grl_source_get_id (GrlSource *source);
 
 const gchar *grl_source_get_name (GrlSource *source);
diff --git a/tools/grilo-inspect/grl-inspect.c b/tools/grilo-inspect/grl-inspect.c
index abe32c6..b054cc6 100644
--- a/tools/grilo-inspect/grl-inspect.c
+++ b/tools/grilo-inspect/grl-inspect.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2011 Igalia S.L.
+ * Copyright (C) 2010-2012 Igalia S.L.
  *
  * Contact: Iago Toral Quiroga <itoral igalia com>
  *
@@ -136,8 +136,6 @@ introspect_source (const gchar *source_id)
     g_print ("Source Details:\n");
     g_print ("  %-20s %s\n", "Identifier:",
              grl_source_get_id (source));
-    g_print ("  %-20s %s\n", "Type:",
-             GRL_IS_MEDIA_SOURCE (source)? "Media Provider": "Metadata Provider");
     g_print ("  %-20s %s\n", "Name:",
              grl_source_get_name (source));
     g_print ("  %-20s %s\n", "Description:",
@@ -151,12 +149,6 @@ introspect_source (const gchar *source_id)
     if (supported_ops & GRL_OP_RESOLVE) {
       g_print ("  grl_metadata_source_resolve():\tResolve Metadata\n");
     }
-    if (supported_ops & GRL_OP_SET_METADATA) {
-      g_print ("  grl_metadata_source_set_metadata():\tSet Metadata\n");
-    }
-    if (supported_ops & GRL_OP_METADATA) {
-      g_print ("  grl_media_source_metadata():\t\tRetrieve Metadata\n");
-    }
     if (supported_ops & GRL_OP_BROWSE) {
       g_print ("  grl_media_source_browse():\t\tBrowse\n");
     }
@@ -172,6 +164,9 @@ introspect_source (const gchar *source_id)
     if (supported_ops & GRL_OP_STORE_PARENT) {
       g_print ("  grl_media_source_store():\t\tAdd New Media\n");
     }
+    if (supported_ops & GRL_OP_STORE_METADATA) {
+      g_print ("  grl_metadata_source_store_metadata():\tStore Metadata\n");
+    }
     if (supported_ops & GRL_OP_REMOVE) {
       g_print ("  grl_media_source_remove():\t\tRemove Media\n");
     }
diff --git a/tools/grilo-test-ui/main.c b/tools/grilo-test-ui/main.c
index b46ef63..a19c29e 100644
--- a/tools/grilo-test-ui/main.c
+++ b/tools/grilo-test-ui/main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2011 Igalia S.L.
+ * Copyright (C) 2010-2012 Igalia S.L.
  * Copyright (C) 2011 Intel Corporation.
  *
  * Contact: Iago Toral Quiroga <itoral igalia com>
@@ -63,7 +63,7 @@ GRL_LOG_DOMAIN_STATIC(test_ui_log_domain);
 /* ----- Other ----- */
 
 #define BROWSE_FLAGS (GRL_RESOLVE_FAST_ONLY | GRL_RESOLVE_IDLE_RELAY)
-#define METADATA_FLAGS (GRL_RESOLVE_FULL | GRL_RESOLVE_IDLE_RELAY)
+#define RESOLVE_FLAGS (GRL_RESOLVE_FULL | GRL_RESOLVE_IDLE_RELAY)
 
 #define WINDOW_TITLE "Grilo Test UI (v." VERSION ")"
 
@@ -140,16 +140,16 @@ typedef struct {
   /* Keeps track of our browsing position and history  */
   GList *source_stack;
   GList *container_stack;
-  GrlMediaSource *cur_source;
+  GrlSource *cur_source;
   GrlMedia *cur_container;
 
   /* Keeps track of the last element we showed metadata for */
-  GrlMediaSource *cur_md_source;
+  GrlSource *cur_md_source;
   GrlMedia *cur_md_media;
 
   /* Keeps track of browse/search state */
   gboolean op_ongoing;
-  GrlMediaSource *cur_op_source;
+  GrlSource *cur_op_source;
   guint cur_op_id;
   gboolean multiple;
 
@@ -193,7 +193,7 @@ static const gchar *ui_definition =
 "</ui>";
 
 static GrlOperationOptions *default_options = NULL;
-static GrlOperationOptions *default_metadata_options = NULL;
+static GrlOperationOptions *default_resolve_options = NULL;
 
 static void show_browsable_sources (void);
 static void quit_cb (GtkAction *action);
@@ -208,9 +208,9 @@ static void load_all_plugins_cb (GtkAction *action);
 static void load_all_plugins (void);
 
 static void changes_notification_cb (GtkToggleAction *action);
-static void content_changed_cb (GrlMediaSource *source,
+static void content_changed_cb (GrlSource *source,
                                 GPtrArray *changed_medias,
-                                GrlMediaSourceChangeType change_type,
+                                GrlSourceChangeType change_type,
                                 gboolean location_unknown,
                                 gpointer data);
 
@@ -269,15 +269,13 @@ changes_notification_cb (GtkToggleAction *action)
     if (grl_source_supported_operations (GRL_SOURCE (source->data)) &
         GRL_OP_NOTIFY_CHANGE) {
       if (ui_state->changes_notification) {
-        grl_media_source_notify_change_start (GRL_MEDIA_SOURCE (source->data),
-                                              NULL);
-        g_signal_connect (GRL_MEDIA_SOURCE (source->data),
+        grl_source_notify_change_start (GRL_SOURCE (source->data), NULL);
+        g_signal_connect (GRL_SOURCE (source->data),
                           "content-changed",
                           G_CALLBACK (content_changed_cb),
                           NULL);
       } else {
-        grl_media_source_notify_change_stop (GRL_MEDIA_SOURCE (source->data),
-                                             NULL);
+        grl_source_notify_change_stop (GRL_SOURCE (source->data), NULL);
         g_signal_handlers_disconnect_by_func (source->data,
                                               content_changed_cb,
                                               NULL);
@@ -299,7 +297,7 @@ create_browser_model (void)
 }
 
 static GtkTreeModel *
-create_metadata_model (void)
+create_resolve_model (void)
 {
   return GTK_TREE_MODEL (gtk_list_store_new (2,
 					     G_TYPE_STRING,     /* name */
@@ -373,7 +371,7 @@ all_keys (void)
 }
 
 static void
-browse_history_push (GrlMediaSource *source, GrlMedia *media)
+browse_history_push (GrlSource *source, GrlMedia *media)
 {
   if (source)
     g_object_ref (source);
@@ -385,12 +383,12 @@ browse_history_push (GrlMediaSource *source, GrlMedia *media)
 }
 
 static void
-browse_history_pop (GrlMediaSource **source, GrlMedia **media)
+browse_history_pop (GrlSource **source, GrlMedia **media)
 {
   GList *tmp;
   tmp = g_list_last (ui_state->source_stack);
   if (tmp) {
-    *source = GRL_MEDIA_SOURCE (tmp->data);
+    *source = GRL_SOURCE (tmp->data);
     ui_state->source_stack = g_list_delete_link (ui_state->source_stack, tmp);
   }
   tmp = g_list_last (ui_state->container_stack);
@@ -402,7 +400,7 @@ browse_history_pop (GrlMediaSource **source, GrlMedia **media)
 }
 
 static void
-set_cur_browse (GrlMediaSource *source, GrlMedia *media)
+set_cur_browse (GrlSource *source, GrlMedia *media)
 {
   if (ui_state->cur_source)
     g_object_unref (ui_state->cur_source);
@@ -419,7 +417,7 @@ set_cur_browse (GrlMediaSource *source, GrlMedia *media)
 }
 
 static void
-set_cur_metadata (GrlMediaSource *source, GrlMedia *media)
+set_cur_resolve (GrlSource *source, GrlMedia *media)
 {
   if (ui_state->cur_md_source)
     g_object_unref (ui_state->cur_md_source);
@@ -450,7 +448,7 @@ clear_panes (void)
     gtk_list_store_clear (GTK_LIST_STORE (view->metadata_model));
     g_object_unref (view->metadata_model);
   }
-  view->metadata_model = create_metadata_model ();
+  view->metadata_model = create_resolve_model ();
   gtk_tree_view_set_model (GTK_TREE_VIEW (view->metadata),
                            view->metadata_model);
 
@@ -518,11 +516,11 @@ value_description (const GValue *value)
 }
 
 static void
-metadata_cb (GrlMediaSource *source,
-             guint operation_id,
-	     GrlMedia *media,
-	     gpointer user_data,
-	     const GError *error)
+resolve_cb (GrlSource *source,
+            guint operation_id,
+            GrlMedia *media,
+            gpointer user_data,
+            const GError *error)
 {
   GList *keys, *i;
   GtkTreeIter iter;
@@ -539,7 +537,7 @@ metadata_cb (GrlMediaSource *source,
     gtk_list_store_clear (GTK_LIST_STORE (view->metadata_model));
     g_object_unref (view->metadata_model);
   }
-  view->metadata_model = create_metadata_model ();
+  view->metadata_model = create_resolve_model ();
   gtk_tree_view_set_model (GTK_TREE_VIEW (view->metadata),
 			   view->metadata_model);
 
@@ -578,7 +576,7 @@ metadata_cb (GrlMediaSource *source,
 
     g_list_free (keys);
 
-    /* Don't free media (we do not ref it when issuing metadata(),
+    /* Don't free media (we do not ref it when issuing resolve(),
        so its reference comes from the treeview and that's freed
        when the treeview is cleared */
 
@@ -596,7 +594,7 @@ metadata_cb (GrlMediaSource *source,
 }
 
 static void
-operation_started (GrlMediaSource *source, guint operation_id,
+operation_started (GrlSource *source, guint operation_id,
                    gboolean multiple)
 {
   ui_state->op_ongoing = TRUE;
@@ -621,12 +619,12 @@ operation_finished (void)
 }
 
 static void
-browse_search_query_cb (GrlMediaSource *source,
-			guint op_id,
-			GrlMedia *media,
-			guint remaining,
-			gpointer user_data,
-			const GError *error)
+browse_search_query_cb (GrlSource *source,
+                        guint op_id,
+                        GrlMedia *media,
+                        guint remaining,
+                        gpointer user_data,
+                        const GError *error)
 {
   gint type;
   const gchar *name;
@@ -710,13 +708,13 @@ browse_search_query_cb (GrlMediaSource *source,
 
    GrlOperationOptions *supported_options = NULL;
    grl_operation_options_obey_caps (options,
-                                    grl_source_get_caps (GRL_SOURCE (source), GRL_OP_SEARCH),
+                                    grl_source_get_caps (source, GRL_OP_SEARCH),
                                     &supported_options,
                                     NULL);
 	switch (state->type) {
 	  case OP_TYPE_BROWSE:
 	    next_op_id =
-	      grl_media_source_browse (source,
+	      grl_source_browse (source,
 				       ui_state->cur_container,
 				       all_keys (),
 				       supported_options,
@@ -725,7 +723,7 @@ browse_search_query_cb (GrlMediaSource *source,
 	    break;
 	  case OP_TYPE_SEARCH:
 	    next_op_id =
-	      grl_media_source_search (source,
+	      grl_source_search (source,
 				       state->text,
 				       all_keys (),
 				       options,
@@ -734,7 +732,7 @@ browse_search_query_cb (GrlMediaSource *source,
 	    break;
 	  case OP_TYPE_QUERY:
 	    next_op_id =
-	      grl_media_source_query (source,
+	      grl_source_query (source,
 				      state->text,
 				      all_keys (),
 				      options,
@@ -771,7 +769,7 @@ browse_search_query_cb (GrlMediaSource *source,
 }
 
 static void
-browse (GrlMediaSource *source, GrlMedia *container)
+browse (GrlSource *source, GrlMedia *container)
 {
   guint browse_id;
   if (source) {
@@ -781,19 +779,19 @@ browse (GrlMediaSource *source, GrlMedia *container)
 
     OperationState *state = g_new0 (OperationState, 1);
     state->type = OP_TYPE_BROWSE;
-    browse_id = grl_media_source_browse (source,
-                                         container,
-                                         all_keys (),
-                                         default_options,
-                                         browse_search_query_cb,
-                                         state);
+    browse_id = grl_source_browse (source,
+                                   container,
+                                   all_keys (),
+                                   default_options,
+                                   browse_search_query_cb,
+                                   state);
     operation_started (source, browse_id, FALSE);
   } else {
     show_browsable_sources ();
   }
 
   set_cur_browse (source, container);
-  set_cur_metadata (NULL, NULL);
+  set_cur_resolve (NULL, NULL);
 }
 
 static void
@@ -806,7 +804,7 @@ browser_activated_cb (GtkTreeView *tree_view,
   GtkTreeIter iter;
   GrlMedia *content;
   gint type;
-  GrlMediaSource *source;
+  GrlSource *source;
   GrlMedia *container;
 
   model = gtk_tree_view_get_model (tree_view);
@@ -841,21 +839,21 @@ browser_activated_cb (GtkTreeView *tree_view,
 }
 
 static void
-metadata (GrlMediaSource *source, GrlMedia *media)
+resolve (GrlSource *source, GrlMedia *media)
 {
   if (source) {
-    /* If source does not support metadata() operation, then use the current
+    /* If source does not support resolve() operation, then use the current
        media */
-    if ((grl_source_supported_operations (GRL_SOURCE (source)) &
-         GRL_OP_METADATA)) {
-          grl_media_source_metadata (source,
-                                     media,
-                                     all_keys (),
-                                     default_metadata_options,
-                                     metadata_cb,
-                                     NULL);
+    if ((grl_source_supported_operations (source) &
+         GRL_OP_RESOLVE)) {
+      grl_source_resolve (source,
+                          media,
+                          all_keys (),
+                          default_resolve_options,
+                          resolve_cb,
+                          NULL);
     } else {
-      metadata_cb (source, 0, media, NULL, NULL);
+      resolve_cb (source, 0, media, NULL, NULL);
     }
   }
 }
@@ -866,7 +864,7 @@ browser_row_selected_cb (GtkTreeView *tree_view,
 {
   GtkTreePath *path = NULL;
   GtkTreeIter iter;
-  GrlMediaSource *source;
+  GrlSource *source;
   GrlMedia *content;
 
   gtk_tree_view_get_cursor (tree_view, &path, NULL);
@@ -882,8 +880,8 @@ browser_row_selected_cb (GtkTreeView *tree_view,
 
   if (source != ui_state->cur_md_source ||
       content != ui_state->cur_md_media) {
-    set_cur_metadata (source, content);
-    metadata (source, content);
+    set_cur_resolve (source, content);
+    resolve (source, content);
   }
 
   /* Check if we can store content in the selected item */
@@ -959,7 +957,7 @@ show_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 static void
 back_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 {
-  GrlMediaSource *prev_source = NULL;
+  GrlSource *prev_source = NULL;
   GrlMedia *prev_container = NULL;
 
   /* TODO: when using dynamic sources this will break
@@ -980,11 +978,11 @@ back_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 }
 
 static void
-store_cb (GrlMediaSource *source,
-	  GrlMediaBox *box,
-	  GrlMedia *media,
-	  gpointer user_data,
-	  const GError *error)
+store_cb (GrlSource *source,
+          GrlMedia *media,
+          GList *failed_keys,
+          gpointer user_data,
+          const GError *error)
 {
   if (error) {
     GRL_WARNING ("Error storing media: %s", error->message);
@@ -1001,7 +999,7 @@ store_btn_clicked_cb (GtkButton *btn, gpointer user_data)
   GtkTreeSelection *sel;
   GtkTreeModel *model = NULL;
   GtkTreeIter iter;
-  GrlMediaSource *source;
+  GrlSource *source;
   GrlMedia *container;
 
   sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->browser));
@@ -1054,8 +1052,8 @@ store_btn_clicked_cb (GtkButton *btn, gpointer user_data)
     grl_media_set_title (media, gtk_entry_get_text (GTK_ENTRY (e1)));
     grl_media_set_description (media,
                                     gtk_entry_get_text (GTK_ENTRY (e3)));
-    grl_media_source_store (source, GRL_MEDIA_BOX (container),
-                            media, store_cb, NULL);
+    grl_source_store (source, GRL_MEDIA_BOX (container),
+                      media, GRL_WRITE_FULL, store_cb, NULL);
   }
 
   gtk_widget_destroy (dialog);
@@ -1069,10 +1067,10 @@ store_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 }
 
 static void
-remove_item_from_view (GrlMediaSource *source, GrlMedia *media)
+remove_item_from_view (GrlSource *source, GrlMedia *media)
 {
   GtkTreeIter iter;
-  GrlMediaSource *iter_source;
+  GrlSource *iter_source;
   GrlMedia *iter_media;
   gboolean found = FALSE;
   gboolean more;
@@ -1099,10 +1097,10 @@ remove_item_from_view (GrlMediaSource *source, GrlMedia *media)
 }
 
 static void
-remove_cb (GrlMediaSource *source,
-	   GrlMedia *media,
-	   gpointer user_data,
-	   const GError *error)
+remove_cb (GrlSource *source,
+           GrlMedia *media,
+           gpointer user_data,
+           const GError *error)
 {
   if (error) {
     GRL_WARNING ("Error removing media: %s", error->message);
@@ -1119,7 +1117,7 @@ remove_btn_clicked_cb (GtkButton *btn, gpointer user_data)
   GtkTreeSelection *sel;
   GtkTreeModel *model = NULL;
   GtkTreeIter iter;
-  GrlMediaSource *source;
+  GrlSource *source;
   GrlMedia *media;
 
   sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->browser));
@@ -1129,7 +1127,7 @@ remove_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 		      BROWSER_MODEL_CONTENT, &media,
 		      -1);
 
-  grl_media_source_remove (source, media, remove_cb, NULL);
+  grl_source_remove (source, media, remove_cb, NULL);
 
   if (source) {
     g_object_unref (source);
@@ -1140,7 +1138,7 @@ remove_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 }
 
 static void
-search (GrlMediaSource *source, const gchar *text)
+search (GrlSource *source, const gchar *text)
 {
   OperationState *state;
   guint search_id;
@@ -1175,7 +1173,7 @@ search (GrlMediaSource *source, const gchar *text)
                                      &supported_options,
                                      NULL);
     g_object_unref (options);
-    search_id = grl_media_source_search (source,
+    search_id = grl_source_search (source,
 					 text,
 					 all_keys (),
 					 supported_options,
@@ -1204,7 +1202,7 @@ search_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 
   if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (view->search_combo),
 				     &iter)) {
-    GrlMediaSource *source;
+    GrlSource *source;
     const gchar *text;
     gtk_tree_model_get (view->search_combo_model, &iter,
 			SEARCH_MODEL_SOURCE, &source,
@@ -1224,7 +1222,7 @@ search_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 }
 
 static void
-query (GrlMediaSource *source, const gchar *text)
+query (GrlSource *source, const gchar *text)
 {
   OperationState *state;
   guint query_id;
@@ -1235,12 +1233,12 @@ query (GrlMediaSource *source, const gchar *text)
   state = g_new0 (OperationState, 1);
   state->text = (gchar *) text;
   state->type = OP_TYPE_QUERY;
-  query_id = grl_media_source_query (source,
-                                     text,
-                                     all_keys (),
-                                     default_options,
-                                     browse_search_query_cb,
-                                     state);
+  query_id = grl_source_query (source,
+                               text,
+                               all_keys (),
+                               default_options,
+                               browse_search_query_cb,
+                               state);
   clear_panes ();
   operation_started (source, query_id, FALSE);
 }
@@ -1252,7 +1250,7 @@ query_btn_clicked_cb (GtkButton *btn, gpointer user_data)
 
   if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (view->query_combo),
 				     &iter)) {
-    GrlMediaSource *source;
+    GrlSource *source;
     const gchar *text;
     gtk_tree_model_get (view->query_combo_model, &iter,
 			QUERY_MODEL_SOURCE, &source,
@@ -1272,7 +1270,7 @@ set_filter_cb (GtkComboBox *widget,
 {
   GrlCaps *caps;
   GrlTypeFilter filter;
-  GrlMediaSource *source;
+  GrlSource *source;
   GtkTreeIter iter;
 
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (view->filter_audio), TRUE);
@@ -1682,8 +1680,8 @@ options_setup (void)
   grl_operation_options_set_skip (default_options, 0);
   grl_operation_options_set_count (default_options, BROWSE_CHUNK_SIZE);
 
-  default_metadata_options = grl_operation_options_new (NULL);
-  grl_operation_options_set_flags (default_metadata_options, METADATA_FLAGS);
+  default_resolve_options = grl_operation_options_new (NULL);
+  grl_operation_options_set_flags (default_resolve_options, RESOLVE_FLAGS);
 }
 
 static void
@@ -1996,7 +1994,7 @@ reset_browse_history (void)
   free_stack (&ui_state->source_stack);
   free_stack (&ui_state->container_stack);
   set_cur_browse (NULL, NULL);
-  set_cur_metadata (NULL, NULL);
+  set_cur_resolve (NULL, NULL);
 }
 
 static void
@@ -2019,9 +2017,9 @@ remove_notification (gpointer data)
 }
 
 static void
-content_changed_cb (GrlMediaSource *source,
+content_changed_cb (GrlSource *source,
                     GPtrArray *changed_medias,
-                    GrlMediaSourceChangeType change_type,
+                    GrlSourceChangeType change_type,
                     gboolean location_unknown,
                     gpointer data)
 {
@@ -2054,7 +2052,7 @@ content_changed_cb (GrlMediaSource *source,
     if (GRL_IS_MEDIA_BOX (media)) {
       message =
         g_strdup_printf ("%s: container '%s' has %s%s",
-                         grl_source_get_name (GRL_SOURCE (source)),
+                         grl_source_get_name (source),
                          media_id? media_id: "root",
                          change_type_string,
                          location_string);
@@ -2085,8 +2083,8 @@ source_added_cb (GrlPluginRegistry *registry,
   GRL_DEBUG ("Detected new source available: '%s'",
              grl_source_get_name (source));
 
-  GRL_DEBUG ("\tPlugin's name: %s", grl_source_get_name (source));
-  GRL_DEBUG ("\tPlugin's description: %s", grl_source_get_description (source));
+  GRL_DEBUG ("\tSource's name: %s", grl_source_get_name (source));
+  GRL_DEBUG ("\tSource's description: %s", grl_source_get_description (source));
 
   /* If showing the plugin list, refresh it */
   if (!ui_state->cur_source && !ui_state->cur_container) {
@@ -2101,13 +2099,11 @@ source_added_cb (GrlPluginRegistry *registry,
   if (ui_state->changes_notification &&
       (grl_source_supported_operations (source) &
        GRL_OP_NOTIFY_CHANGE)) {
-    if (grl_media_source_notify_change_start (GRL_MEDIA_SOURCE (source), NULL)) {
-      g_signal_connect (GRL_MEDIA_SOURCE (source), "content-changed",
+    if (grl_source_notify_change_start (GRL_SOURCE (source), NULL)) {
+      g_signal_connect (GRL_SOURCE (source), "content-changed",
                         G_CALLBACK (content_changed_cb), NULL);
     }
   }
-
-  /* Activate filters by type (if supported) */
 }
 
 static void
diff --git a/tools/vala/grilo-test.vala b/tools/vala/grilo-test.vala
index c7bee94..be0b242 100644
--- a/tools/vala/grilo-test.vala
+++ b/tools/vala/grilo-test.vala
@@ -1,7 +1,7 @@
 using Grl;
 
 public class SimplePlaylist : Object {
-	private GLib.List<MediaSource> source_list;
+	private GLib.List<Grl.Source> source_list;
 	MainLoop main_loop = new MainLoop (null, false);
 	int processed_sources = 0;
 
@@ -21,7 +21,7 @@ public class SimplePlaylist : Object {
 		var ops = source.supported_operations ();
 		if ((ops & Grl.SupportedOps.SEARCH) != 0) {
 			debug ("Detected new source availabe: '%s' and it supports search", source.get_name ());
-			source_list.append (source as MediaSource);
+			source_list.append (source as Grl.Source);
 			debug ("source list size = %u", source_list.length ());
 		}
 	}
@@ -33,7 +33,7 @@ public class SimplePlaylist : Object {
 	public SimplePlaylist () {
 	}
 
-	private void search_cb (Grl.MediaSource source,
+	private void search_cb (Grl.Source source,
 							uint browse_id,
 							Grl.Media? media,
 							uint remaining,
@@ -63,13 +63,9 @@ public class SimplePlaylist : Object {
 	public void search (string q) {
 		unowned GLib.List keys = Grl.MetadataKey.list_new (Grl.MetadataKey.ID, Grl.MetadataKey.TITLE, Grl.MetadataKey.URL);
 
-		foreach (MediaSource source in source_list) {
+		foreach (Grl.Source source in source_list) {
 			debug ("%s - %s", source.get_name (), q);
-			var caps = source.get_caps (Grl.SupportedOps.SEARCH);
-			var options = new Grl.OperationOptions (caps);
-			options.set_count (100);
-			options.set_flags (Grl.MetadataResolutionFlags.FULL | Grl.MetadataResolutionFlags.IDLE_RELAY);
-			source.search (q, keys, options, search_cb);
+			source.search (q, keys, 0, 100, Grl.ResolutionFlags.FULL | Grl.ResolutionFlags.IDLE_RELAY, search_cb);
 		}
 	}
 



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