Sources provide access to media content. Examples of them are sources providing content from Jamendo or UPnP. Sources can also provide other information that complements already existent media content. Thus, there can be sources providing content and others adding more informationover that content.
Sources are provided by plugins. A plugin usually provides one source, but it can provide more than one. For instance, the UPnP plugin is able to provide several sources, one source per each UPnP server found in the network.
Usually, clients interact with these sources in various ways:
Grilo plugins must use the macro GRL_PLUGIN_REGISTER(), which defines the entry and exit points of the plugin (called when the plugin is loaded and unloaded respectively) as well as its plugin identifier (a string identifying the plugin).
The plugin identifier will be used by clients to identify the plugin when interacting with the plugin registry API. See the GrlRegistry API reference for more details.
The plugin initialization function is mandatory. The plugin deinitialization function is optional.
Usually the plugin initialization function will create at least one GrlSource instance and register it using grl_registry_register_source().
A GrlSource instance represents a particular source of media/attributes. Usually each plugin would spawn just one media source, but some plugins may spawn multiple sources. For example, a UPnP plugin spawning one media source object for each UPnP server discovered.
Users can query the registry for available sources and then use the GrlSource API to interact with them.
If the plugin requires configuration this should be processed during the plugin initialization function, which should return TRUE upon successful initialization or FALSE otherwise.
The parameter "configs" of the plugin initialization function provides available configuration information provided by the user for this plugin, if any. This parameter is a list of GrlConfig objects. Usually there would be only one GrlConfig object in the list, but there might be more in the cases of plugins spawning multiple media sources that require different configuration options.
gboolean grl_foo_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { gchar *api_key; GrlConfig *config; config = GRL_CONFIG (configs->data); api_key = grl_config_get_api_key (config); if (!api_key) { GRL_INFO ("Missing API Key, cannot load plugin"); return FALSE; } GrlFooSource *source = grl_foo_source_new (api_key); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); g_free (api_key); return TRUE; } GRL_PLUGIN_REGISTER (grl_foo_plugin_init, NULL, "grl-foo");
The next step is to implement the source code, for that sources must extend the GrlSource class.
In typical GObject fashion, developers should use the G_DEFINE_TYPE macro, and then provide the class initialization function (grl_foo_source_class_init() in the example below) and the instance initialization function (grl_foo_source_init() in the example below). A constructor function, although not mandatory, is usually nice to have (grl_foo_source_new() in the example below).
When creating a new GrlSource instance, a few properties should be provided:
In the class initialization function the plugin developer should provide implementations for the operations that the plugin will support. Almost all operations are optional, but for typically Search or Browse are expected in sources providing media content, and Resolve for sources providing information for existent content.
/* Foo class initialization code */ static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->supported_keys = grl_foo_source_supported_keys; source_class->slow_keys = grl_foo_source_slow_keys; source_class->browse = grl_foo_source_browse; source_class->search = grl_foo_source_search; source_class->query = grl_foo_source_query; source_class->store = grl_foo_source_store; source_class->remove = grl_foo_source_remove; source_class->resolve = grl_foo_source_resolve; } /* Foo instance initialization code */ static void grl_foo_source_init (GrlFooSource *source) { /* Here you would initialize 'source', which is an instance of this class type. */ source->api_key = NULL; } /* GrlFooSource constructor */ static GrlFooSource * grl_foo_source_new (const gchar *api_key) { GrlFooSource *source; source = GRL_FOO_SOURCE (g_object_new (GRL_FOO_SOURCE_TYPE, "source-id", "grl-foo", "source-name", "Foo", "source-desc", "Foo media provider", NULL)); source->api_key = g_strdup (api_key); return source; } G_DEFINE_TYPE (GrlFooSource, grl_foo_source, GRL_TYPE_SOURCE);
Sources should implement "supported_keys" method to define what metadata keys the source is able to handle.
This method is declarative, and it only has to return a list of metadata keys that the plugin supports, that is, it is a declaration of the metadata that the plugin can provide for the media content that it exposes.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->supported_keys = grl_foo_source_supported_keys; } static const GList * grl_foo_source_supported_keys (GrlSource *source) { static GList *keys = NULL; if (!keys) { keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID, GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_THUMBNAIL, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_ARTIST, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_INVALID); } return keys; }
This method is similar to "supported_keys", and in fact it returns a subset of the keys returned by "supported_keys".
This method is intended to provide the framework with information on metadata that is particularly expensive for the framework to retrieve. The framework (or the plugin users) can then use this information to remove this metadata from their requests when performance is important. This is, again, a declarative interface providing a list of keys.
If the plugin does not provide an implementation for "slow_keys" the framework assumes that all keys are equally expensive to retrieve and will not perform optimizations in any case.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->slow_keys = grl_foo_source_slow_keys; } static const GList * grl_foo_source_slow_keys (GrlSource *source) { static GList *keys = NULL; if (!keys) { keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL); } return keys; }
This method implements free-text based searches, retrieving media that matches the text provided by the user.
Typically, the way this method operates is like this:
Below you can see some source code that illustrates this process:
/* In this example we assume a media provider that can be queried over http, and that provides its results in xml format */ static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->search = grl_foo_source_search; } static void foo_execute_search_async_cb (gchar *xml, GrlSourceSearchSpec *ss) { GrlMedia *media; gint count; count = count_results (xml); if (count == 0) { /* Signal "no results" */ ss->callback (ss->source, ss->operation_id, NULL, 0, ss->user_data, NULL); } else { /* parse_next parses the next media item in the XML and creates a GrlMedia instance with the data extracted */ while (media = parse_next (xml)) ss->callback (ss->source, /* Source emitting the data */ ss->operation_id, /* Operation identifier */ media, /* Media that matched the query */ --count, /* Remaining count */ ss->user_data, /* User data for the callback */ NULL); /* GError instance (if an error occurred) */ } } static void grl_foo_source_search (GrlSource *source, GrlSourceSearchSpec *ss) { gchar *foo_http_search: foo_http_search = g_strdup_printf("http://media.foo.com?text=%s&offset=%d&count=%d", ss->text, grl_operation_options_get_skip (ss->options), grl_operation_options_get_count (ss->options)); /* This executes an async http query and then invokes foo_execute_search_async_cb with the response */ foo_execute_search_async (foo_http_search, ss); }
Please, check Common considerations for Search, Browse and Query implementations for more information on how to implement Search operations properly.
Examples of plugins implementing Search functionality are grl-jamendo, grl-youtube or grl-vimeo among others.
Browsing is an interactive process, where users navigate by exploring these containers exposed by the media source in hierarchical form. The idea of browsing a media source is the same as browsing a file system.
The signature and way of operation of the Browse operation is the same as in the Search operation with one difference: instead of a text parameter with the search keywords, it receives a GrlMedia object representing the container the user wants to browse.
For the most part, plugin developers that write Browse implementations should consider the same rules and guidelines explained for Search operations.
Below you can see some source code that illustrates this process:
/* In this example we assume a media provider that can be queried over http, providing results in XML format. The media provider organizes content according to a list of categories. */ static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->browse = grl_foo_source_browse; } static void foo_execute_categories_async_cb (gchar *xml, GrlSourceBrowseSpec *bs) { GrlMedia *media; gint count; count = count_results (xml); if (count == 0) { /* Signal "no results" */ bs->callback (bs->source, bs->operation_id, NULL, 0, bs->user_data, NULL); } else { /* parse_next parses the next category item in the XML and creates a GrlMedia instance with the data extracted, which should be a container */ while (media = parse_next_cat (xml)) bs->callback (bs->source, /* Source emitting the data */ bs->operation_id, /* Operation identifier */ media, /* The category container */ --count, /* Remaining count */ bs->user_data, /* User data for the callback */ NULL); /* GError instance (if an error occurred) */ } } static void foo_execute_media_async_cb (gchar *xml, GrlSourceBrowseSpec *os) { GrlMedia *media; gint count; count = count_results (xml); if (count == 0) { /* Signal "no results" */ bs->callback (bs->source, bs->operation_id, NULL, 0, bs->user_data, NULL); } else { /* parse_next parses the next media item in the XML and creates a GrlMedia instance with the data extracted */ while (media = parse_next_media (xml)) os->callback (os->source, /* Source emitting the data */ os->operation_id, /* Operation identifier */ media, /* Media that matched the query */ --count, /* Remaining count */ os->user_data, /* User data for the callback */ NULL); /* GError instance (if an error occurred) */ } } static void grl_foo_source_browse (GrlSource *source, GrlSourceBrowseSpec *bs) { gchar *foo_http_browse: /* We use the names of the categories as their media identifiers */ container_id = grl_media_get_id (bs->container); if (!container_id) { /* Browsing the root container, the result must be the list of categories provided by the service */ foo_http_browse = g_strdup_printf("http://media.foo.com/category_list", grl_operation_options_get_skip (bs), grl_operation_options_get_count (bs)); /* This executes an async http query and then invokes foo_execute_categories_async_cb with the response */ foo_execute_categories_async (foo_http_browse, bs); } else { /* Browsing a specific category */ foo_http_browse = g_strdup_printf("http://media.foo.com/content/%s?offset=%d&count=%d", container_id, grl_operation_options_get_skip (bs), grl_operation_options_get_count (bs)); /* This executes an async http query and then invokes foo_execute_browse_async_cb with the response */ foo_execute_media_async (foo_http_browse, bs); } }
Some considerations that plugin developers should take into account:
Please, check Common considerations for Search, Browse and Query implementations for more information on how to implement Browse operations properly.
Examples of plugins implementing browse functionality are grl-jamendo, grl-filesystem or grl-upnp among others.
This method provides plugin developers with means to expose service-specific functionality that cannot be achieved through regular Browse and Search operations.
This method operates just like the Search method, but the text parameter does not represent a list of keywords to search for, instead, its meaning is plugin specific and defined by the plugin developer. Plugin documentation should explain what is the syntax of this query text, and what it allows.
Normally, Query implementations involve parsing and decoding this input string into something meaningful for the media provider (a specific operation with its parameters).
Usually, Query implementations are intended to provide advanced filtering capabilities and similar features that make use of specific features of the service that cannot be exposed through more service agnostic APIs, like Search or Browse. For example, a plugin that provides media content stored in a database can implement Query to give users the possibility to execute SQL queries directly, by encoding the SQL commands in this input string, giving a lot of flexibility in how they access the content stored in the database in exchange for writing plugin-specific code in the application.
The example below shows the case of a plugin implementing Query to let the user specify filters directly in SQL format for additional flexibility.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->query = grl_foo_source_query; } static void grl_foo_source_query (GrlSource *source, GrlSourceQuerySpec *qs) { const gchar *sql_filter; GList *results; GrlMedia *media; gint count; /* In this example, we are assuming qs->query is expected to contain a suitable SQL filter */ sql_query = prepare_sql_with_custom_filter (qs->query, grl_operation_options_get_skip (qs->options), grl_operation_options_get_skip (qs->options)); /* Execute the resulting SQL query, which incorporates the filter provided by the user */ results = execute_sql (sql_query); /* For each result obtained, invoke the user callback as usual */ count = g_list_length (results); if (count == 0) { /* Signal "no results" */ qs->callback (qs->source, qs->operation_id, NULL, 0, qs->user_data, NULL); } else { while (media = next_result (&results)) qs->callback (qs->source, /* Source emitting the data */ qs->operation_id, /* Operation identifier */ media, /* Media that matched the query */ --count, /* Remaining count */ qs->user_data, /* User data for the callback */ NULL); /* GError instance (if an error occurred) */ } }
Please, check Common considerations for Search, Browse and Query implementations for more information on how to implement Query operations properly.
Examples of plugins implementing Query are grl-jamendo, grl-upnp or grl-bookmarks among others.
Resolve operations are issued in order to grab additional information on a given media (GrlMedia).
Typically, the use case for Resolve operations is applications obtaining a list of GrlMedia objects by executing a Browse, Search or Query operation, requesting limited metadata (for performance reasons), and then requesting additional metadata for specific items selected by the user.
This additional information can be provided by the same source that got the GrlMedia objects (if it implements the Resolve operation), or by other sources able to provide the information requested.
Plugins implementing "resolve" operation should implement "may_resolve" too. The purpose of this method is to analyze if the GrlMedia contains the required metadata for the source to provide the additional metadata requested. If not provided, the default behaviour for sources implementing "resolve" but not "may_resolve" is to resolve only supported keys in media objects coming from the source itself.
/* In this example we assume a plugin that can resolve thumbnail information for audio items given that we have artist and album information available */ static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->may_resolve = grl_foo_source_may_resolve; source_class->resolve = grl_foo_source_resolve; } static gboolean grl_foo_source_may_resolve (GrlSource *source, GrlMedia *media, GrlKeyID key_id, GList **missing_keys) { gboolean needs_artist = FALSE; gboolean needs_album = FALSE; /* We only support thumbnail resolution */ if (key_id != GRL_METADATA_KEY_THUMBNAIL) return FALSE; /* We only support audio items */ if (media) { if (!grl_media_is_audio (media)) return FALSE; /* We need artist information available */ if (grl_media_get_artist (media) == NULL) { if (missing_keys) *missing_keys = g_list_add (*missing_keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ARTIST)); needs_artist = TRUE; } /* We need album information available */ if (grl_media_get_album (media) == NULL)) { if (missing_keys) *missing_keys = g_list_add (*missing_keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ALBUM)); needs_album = TRUE; } } if (needs_album || needs_artist) return FALSE; return TRUE; } static void grl_foo_source_resolve (GrlSource *source, GrlSourceResolveSpec *rs) { const gchar *album; const gchar *artist, gchar *thumb_uri; const GError *error = NULL; if (contains_key (rs->keys, GRL_METADATA_KEY_THUMBNAIL) { artist = grl_media_get_artist (rs->media); album = grl_media_get_album (rs->media); if (artist && album) { thumb_uri = resolve_thumb_uri (artist, album); grl_media_set_thumbnail (rs->media, thumb_uri); } else { error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED, "Can't resolve thumbnail, artist and album not known"); } } else { error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED, "Can't resolve requested keys"); } rs->callback (source, rs->operation_id, rs->media, rs->user_data, error); if (error) g_error_free (error); }
Some considerations that plugin developers should take into account:
Examples of plugins implementing Resolve are grl-youtube, grl-upnp or grl-lastfm-albumart among others.
The Store method is used to push new content to the media source.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlMediaSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->store = grl_foo_source_store; } static void grl_foo_source_store (GrlSource *source, GrlSourceStoreSpec *ss) { const gchar *title; const gchar *uri; const gchar *parent_id; guint row_id; /* We get the id of the parent container where we want to put the new content */ parent_id = grl_media_get_id (GRL_MEDIA (parent)); /* We get he metadata of the media we want to push, in this case only URI and Title */ uri = grl_media_get_uri (); title = grl_media_get_title (); /* Push the data to the media provider (in this case a database) */ row_id = run_sql_insert (parent_id, uri, title); /* Set the media id in the GrlMedia object */ grl_media_set_id (ss->media, row_id_to_media_id (row_id)); /* Inform the user that the operation is done (NULL error means everything was ok), and all the keys were stored successfully (no list of failed keys) */ ss->callback (ss->source, ss->media, NULL, ss->user_data, NULL); }
Some considerations that plugin developers should take into account:
Examples of plugins implementing Store are grl-bookmarks or grl-podcasts.
Some plugins may provide users with the option of updating the metadata available for specific media items. For example, a plugin may store user metadata like the last time that a certain media resource was played or its play count. These metadata properties do not make sense if applications do not have means to change and update their values.
Plugins that support this feature must implement two methods:
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->writable_keys = grl_foo_source_writable_keys; source_class->store_metadata = grl_foo_source_store_metadata; } static const GList * grl_foo_source_writable_keys (GrlSource *source) { static GList *keys = NULL; if (!keys) { keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING, GRL_METADATA_KEY_PLAY_COUNT, GRL_METADATA_KEY_LAST_PLAYED, NULL); } return keys; } static void grl_foo_source_store_metadata (GrlSource *source, GrlSourceSetMetadataSpec *sms) { GList *iter; const gchar *media_id; GList *failed_keys = NULL; /* 'sms->media' contains the media with updated values */ media_id = grl_media_get_id (sms->media); /* Go through all the keys that need update ('sms->keys'), take the new values (from 'sms->media') and update them in the media provider */ iter = sms->keys; while (iter) { GrlKeyID key = GRLPOINTER_TO_KEYID (iter->data); if (!foo_update_value_for_key (sms->media, key)) { /* Save a list with keys that we failed to update */ failed_keys = g_list_prepend (failed_keys, iter->data); } iter = g_list_next (iter); } /* We are done, invoke user callback to signal client */ sms->callback (sms->source, sms->media, failed_keys, sms->user_data, NULL); g_list_free (failed_keys); }
Some considerations that plugin developers should take into account:
Examples of plugins implementing "store_metadata" are grl-metadata-store or grl-tracker.
The Remove method is used to remove content from the media source.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->remove = grl_foo_source_remove; } static void grl_foo_source_remove (GrlSource *source, GrlSourceRemoveSpec *rs) { /* Remove the data from the media provider (in this case a database) */ run_sql_delete (ss->media_id); /* Inform the user that the operation is done (NULL error means everything was ok */ rs->callback (rs->source, rs->media, rs->user_data, NULL); }
Examples of plugins implementing Remove are grl-bookmarks or grl-podcasts.
Some times clients have access to the URI of the media, and they want to retrieve metadata for it. A couple of examples where this may come in handy: A file system browser that needs to obtain additional metadata for a particular media item located in the filesystem. A browser plugin that can obtain additional metadata for a media item given its URL. In these cases we know the URI of the media, but we need to create a GrlMedia object representing it.
Plugins that want to support URI to GrlMedia conversions must implement the "test_media_from_uri" and "media_from_uri" methods.
The method "test_media_from_uri" should return TRUE if, upon inspection of the media URI, the plugin decides that it can convert it to a GrlMedia object. For example, a YouTube plugin would check that the URI of the media is a valid YouTube URL. This method is asynchronous and should not block. If the plugin cannot decide if it can or cannot convert the URI to a GrlMedia object by inspecting the URI without doing blocking operations, it should return TRUE. This method is used to discard efficiently plugins that cannot resolve the media.
The method "media_from_uri" is used to do the actual conversion from the URI to the GrlMedia object.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->test_media_from_uri = grl_foo_source_test_media_from_uri; source_class->media_from_uri = grl_foo_source_media_from_uri; } static gboolean grl_filesystem_test_media_from_uri (GrlSource *source, const gchar *uri) { if (strstr (uri, "http://media.foo.com/media-info/") == uri) { return TRUE; } return FALSE; } static void grl_filesystem_media_from_uri (GrlSource *source, GrlSourceMediaFromUriSpec *mfus) { gchar *media_id; GrlMedia *media; media_id = get_media_id_from_uri (mfus->uri); media = create_media_from_id (media_id); mfus->callback (source, mfus->media_from_uri_id, media, mfus->user_data, NULL); g_free (media_id); }
Some considerations that plugin developers should take into account:
Examples of plugins implementing "media_from_uri" are grl-filesystem or grl-youtube.
Source can signal clients when available media content has been changed. This is an optional feature.
Plugins supporting content change notification must implement "notify_change_start" and "notify_change_stop", which let the user start or stop content change notification at will.
Once users have activated notifications by invoking "notify_change_start", media sources should communicate any changes detected by calling grl_source_notify_change_list() with a list of the media items changed.
Upon calling "notify_changes_stop" the plugin must stop communicating changes until "notify_changes_start" is called again.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->notify_change_start = grl_foo_source_notify_change_start; source_class->notify_change_stop = grl_foo_source_notify_change_stop; } static void content_changed_cb (GList *changes) { GPtrArray *changed_medias; changed_medias = g_ptr_array_sized_new (g_list_length (changes)); while (media = next_media_from_changes (&changes)) { g_ptr_array_add (changed_medias, media); } grl_source_notify_change_list (source, changed_medias, GRL_CONTENT_CHANGED, FALSE); } static gboolean grl_foo_source_notify_change_start (GrlSource *source, GError **error) { GrlFooSource *foo_source; /* Listen to changes in the media content provider */ foo_source = GRL_FOO_SOURCE (source); foo_source->listener_id = foo_subscribe_listener_new (content_changed_cb); return TRUE; } static gboolean grl_foo_source_notify_change_stop (GrlSource *source, GError **error) { GrlFooSource *foo_source; /* Stop listening to changes in the media content provider */ foo_source = GRL_FOO_SOURCE (source); foo_listener_destroy (foo_source->listener_id); return TRUE; }
Please check the GrlSource API reference for more details on how grl_source_notify_change_list() should be used.
Examples of plugins implementing change notification are grl-upnp and grl-tracker among others
Implementing the "cancel" method is optional, as others. This method provided means for application developers to cancel ongoing operations on metadata sources (and hence, also in media sources).
The "cancel" method receives the identifier of the operation to be cancelled.
Typically, plugin developers would implement cancellation support by storing relevant information for the cancellation process along with the operation data when this is started, and then retrieving this information when a cancellation request is received.
Grilo provides plugin developers with API to attach arbitrary data to a certain operation given its identifier. These APIs are:
See the API reference documentation for grl-operation for more details.
static void grl_foo_source_class_init (GrlFooSourceClass * klass) { GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); source_class->search = grl_foo_source_search; source_class->cancel = grl_foo_source_cancel; } static void grl_foo_source_search (GrlSource *source, GrlSourceSearchSpec *ss) { ... gint op_handler = foo_service_search_start (ss->text, ...); grl_operation_set_data (ss->operation_id, GINT_TO_POINTER (op_handler)); ... } static void grl_foo_source_cancel (GrlSource *source, guint operation_id) { gint op_handler; op_handler = GPOINTER_TO_INT (grl_operation_get_data (operation_id)); if (op_handler > 0) { foo_service_search_cancel (op_handler); } }
Some examples of plugins implementing cancellation support are grl-youtube, grl-jamendo or grl-filesystem, among others.
Developers must free any data stored before the operation finishes.
Grilo ships a GTK+ test user interface called grilo-test-ui that can be used to test new plugins. This simple playground application can be found in the 'grilo' core source code under tools/grilo-test-ui/. If you have Grilo installed on your system, you may have this application installed as well.
This application loads plugins from the default plugin installation directory in your system or, alternatively, by inspecting the GRL_PLUGIN_PATH environment variable, which can be set to contain a list of directories where Grilo should look for plugins.
Once the plugin library is visible to Grilo one only has to start the grilo-test-ui application and it will load it along with other Grilo plugins available in the system.
In case there is some problem with the initialization of the plugin it should be logged on the console. Remember that you can control the amount of logging done by Grilo through the GRL_DEBUG environment variable. You may want to set this variable to do full logging, in which case you should type this in your console:
$ export GRL_DEBUG="*:*"
If you want to focus only on logging the plugin loading process, configure Grilo to log full details from the plugin registry module alone by doing this instead:
$ export GRL_DEBUG="registry:*"
In case your plugin has been loaded successfully you should see something like this in the log:
(lt-grilo-test-ui:14457): Grilo-DEBUG: [registry] grl-registry.c:188: Plugin rank [plugin-id]' : 0 (lt-grilo-test-ui:14457): Grilo-DEBUG: [registry] grl-registry.c:476: New source available: [source-id] (lt-grilo-test-ui:14457): Grilo-DEBUG: [registry] grl-registry.c:683: Loaded plugin '[plugin-id]' from '[plugin-file-absolute-path.so]'
If your plugin is a Media Source (not a Metadata Source) you should be able to see it in the user interface of grilo-test-ui like this:
If your plugin is a Metadata Source then you should test it by doing a Browse, Search or Query operation in some other Media Source available and then click on any of the media items showed as result. By doing this grilo-test-ui will execute a Metadata operation which would use any available metadata plugins to gather as much information as possible. Available metadata obtained for the selected item will be shown in the right pane for users to inspect.
For offline testing of plug-ins, particularly in automated tests, it is useful to simulate and return predefined network replies. Therefore, Grilo provides a few facilities for mocking network replies.
To enable mocking, set the environment variable GRL_NET_MOCKED. The value of this variable is interpreted as the path of the mock configuration file to use. This file is a simple .ini file, split into a "default" section and one section per URL to mock.
[default] version = 1 ignored-parameters = field1[;field2[;...]] or "*" [http://www.example.com] data = content/of/response.txt timeout = 500
An easy way to capture the responses is to run your application with the environment variable GRL_NET_CAPTURE_DIR. GrlNetWc will then write each response into a file following the pattern "url-timestamp". If the directory does not exist yet then it will be created.
This section needs to be present in any mock reply configuration file.
version
needs to be "1".
ignored-parameters
is a semicolon separated list of
query parameter names that can be used to map URLs to sections
without paying attention to query parameters listed here.
By setting a value of "api_key" a request for
http://www.example.com?q=test+query&api_key=dummy
will be answered with the mock data for
http://www.example.com?q=test+query
.
Setting "api_key;q" or "*" will result in mock answer for
http://www.example.com
.
The section title is used to map URLs to response files.
data
is a path to a text file containing the
raw response of the websserver. The path may be relative to this
configuration file or an absolute path.
timeout
may be used to delay the response in
seconds. The default is to not delay at all.
Skip the data
field to provoke a "not found" error.