Lua sources

Overview of Lua sources
Writing Lua sources
Configuring Lua sources
Lua-Factory library

Overview of Lua sources

By using the Lua-Factory plugin it is possible to write Grilo sources in Lua. This has several advantages over the standard C source such as abstracting the GObjectification of GrlSource into a simple Lua script.

There are a few disavantages as well. Not all Grilo operations are implemented in Lua-Factory, only the most used ones: Search, Browse, Query and Resolve; Also, extending the Lua source with other Lua libraries is possible but has its own set of problems related to compatibility and security thus is not encouraged (Lua-Factory provides its own library which helps general purpose plugins).

In all, you can write faster Grilo sources with less complexity if your source match Lua-Factory limitations. In the following sections you can learn how to write Lua sources.

Writing Lua sources

Overall, the high level steps of a Grilo source written in Lua is the same as one written in C: Initialization and Source operations. The finalization of the source is taken care by Lua-Factory and Lua's Garbage Collector.

The initialization is done through a global table called "source" where you should define information like the id and name of the source. There several other options which are described at Registering your lua source.

Besides the global table, Lua-Factory will try to call "grl_source_init" function with the configuration keys provided to your source. This can be used to verify and store localy the configuration keys.

Now, let's take a look at all Grilo operations that you can implement in Lua sources;

Source initialization

The initialization of the source happens when a client application requests the source from grilo. This usually happens during the client application start. Because of this, this is a good place to do all the synchronous calls to the grilo Lua API and do all the preparatory requests to the remote servers.

In case of a GOA-integrated plugin, it is also a good place to request all the needed information from GOA.

The single function that is responsible for the source initialization is grl_source_init.

grl_source_init

Parameters: Table where each key is the config-key's name and the value is the one provided by Application;

Return value: Return true if the source can be initialize, false otherwise.

Description: This function is used to initialize the plugin. Check and store all the configuration parameters which are required, request GOA for credentials.

The example below shows the initialization, that first tries to get the needed credentials from the config, and if there is none, from the GOA.

-- Global config table which requires username and password
source = {
  ...
  config_keys = {
    required = { "username", "password" },
  },
  ...
  goa_account_provider = "my_goa_type",
  goa_account_feature = "music",
  ...
}

-- Global table to store config data
ldata = {}

function grl_source_init(config)
  if (config) then
    if (config.username) and (config.password) then
      ldata.username = configs.username
      ldata.password = configs.password
      ldata.from_goa = false
      return true
    end
  end

  ldata.oauth2_token = grl.goa_access_token ()
  ldata.from_goa = true

  if (not oauth2_token) then
    return false
  end

  return true
end
    
Grilo operations

There are four grilo operations that could be implemented in Lua plugin: grl_source_search, grl_source_browse, grl_source_query and grl_source_resolve.

The calls to all of this functions is somewhat similar: all of them have three parameters, with the first parameter being either a media or a string with which the plugin is expected to work, the second parameter being the key-value table of the operation options, and the third parameter being the callback function. And while the first parameter is trivial (either a string or a key-value table of GrlMedia), the other two need some explanation.

The options is a key-value table that represents the compilation of the various operation options. It more or less follows the structure of the GrlOperationOptions class, with type filter, filters and range filters being all placed in the "filters" table. The type filter can be found there by the key "type", while all the other filters can be found by their names. The table also includes the requested keys table. The whole options table can be shown as this:

options = {
  count = <int>,
  skip  = <int>,
  operation_id = <int>,
  requested_keys = {"<key1>", "<key2>", ...}
  filters = {
    type = {
      audio = <boolean>,
      video = <boolean>,
      image = <boolean>
    }
    <filter name> = {
      value = <filter value>
    }
    <range filter name> = {
      min = <range filter value>
      max = <range filter value>
    }
  }
}
    

The value inside the filters can be a number, a string, or a datetime, in which case it's an integer representing unixtime.

The callback is a function that is a wrapper for it's C counterpart. It doesn't require an explicit operation_id as a parameter, but is valid only for the operation to which it was provided. It is both pointless and harmful to store the callback from one operation, and use it for another.

The function accepts two parameters, both of which are optional: media which can be a table or a nil, and remaining which is integer. Just like in C version, the media is returned as a result of the current operation, and remaining should be either a non-negative integer or -1 if the remaining number is unknown. If the remaining parameter is omitted, it is considered to be zero. If the second parameter is zero, the callback is considered to be the finishing callback. Calling callback again after the finishing callback is done is considered an error. Calling callback with no parameters at all is also a valid way to make a finishing callback.

The media returned with the callback should be a key-value table. Lua-factory will attempt to reconstruct the media object with the provided data, but if the key would be unknown or the type of the value would be incompatible, the key-value pair would be ignored, and the warning would be issued. It is advisable to add a "type" key to the table, with the value being "audio", "video", "image" or "container" depending on the type of media. Without this parameter the reconstructed media will have a generic type, which may be undesirable in some cases (e.g. the media will be unbrowseable without the "container" type).

Every operation should end with a finishing callback. Leaving operation unfinished is considered an error. And even though lua-factory will handle such a situation by making the finishing callback itself, one should not rely on this behavior on a regular basis.

grl_source_search

Parameters: String: text provided by application. Table: a key-value table with the search options. Function: a callback function with which the results should be returned.

Return value: None.

Description: Similar to its C version.

The following example shows what the port of the C example function would look like.

-- In this example we assume a media provider that can be
-- queried over http, and that provides its results in xml format


function foo_search_async_cb (results, callback)
  count = count_results (results);

  if (count == 0) then
    -- Signal "no results"
    callback ();
  else
    -- parse_next parses the next media item in the XML
    -- and creates a key-value table with the data extracted
    media = parse_next (results)
    while (media ~= nil) do
      count -= 1
      callback (media, count)
      media = parse_next (results)
    end
  end
end

function grl_source_search (query, options, callback)
  foo_http_search =
    string.format ("http://media.foo.com?text=%s&offset=%d&count=%d",
                   ss->text,
                   options.count),
                   options.skip)

  -- asynchronously execute an http query with callback and userdata
  -- userdata in our case is grilo callback
  grl.fetch (foo_http_search, foo_search_async_cb, callback)
end
    
grl_source_browse

Parameters: Table: a key-value table of the container to browse. Table: a key-value table with the search options. Function: a callback function with which the results should be returned.

Return value: None.

Description: Similar to its C version.

The following example shows what the port of the C example function would look like.

-- 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.

function foo_categories_async_cb (results, callback)
  count = count_results (xml);

  if (count == 0) then
    -- Signal "no results"
    callback ()
  else
    -- parse_next parses the next category item in the XML
    -- and creates a key-value table with the data extracted,
    -- media.type = "container"
    media = parse_next_cat (results)
    while (media ~= nil) do
      count -= 1
      callback (media, count)
      media = parse_next_cat (results)
   end
  end
end

function foo_media_async_cb (gchar *xml, GrlSourceBrowseSpec *os)
  count = count_results (xml);

  if (count == 0) then
    -- Signal "no results"
    callback ()
  else
    -- parse_next parses the next category item in the XML
    -- and creates a key-value table with the data extracted,
    media = parse_next_media (results)
    while (media ~= nil) do
      count -= 1
      callback (media, count)
      media = parse_next_media (results)
   end
  end
end


function grl_source_browse (media, options, callback)
  if (not media) or (not media.id) then
    -- Browsing the root container, the result must be the list of
    -- categories provided by the service
    foo_http_browse =
      string.format("http://media.foo.com/category_list?offset=%d&count=%d",
                    options.skip,
                    options.count);
    -- asynchronously execute an http query with callback and userdata
    -- userdata in our case is grilo callback
    grl.fetch (foo_http_browse, foo_categories_async_cb, callback);
  else
    -- Browsing a specific category
    -- We use the names of the categories as their media identifiers
    foo_http_browse =
      string.format("http://media.foo.com/content/%s?offset=%d&count=%d",
                    media.id,
                    options.skip,
                    options.count)
    -- asynchronously execute an http query with callback and userdata
    -- userdata in our case is grilo callback
    grl.fetch (foo_http_browse, media_async_cb, callback);
  end
end
    
grl_source_query

Parameters: String: text provided by application. Table: a key-value table with the search options. Function: a callback function with which the results should be returned.

Return value: None.

Description: Similar to its C version.

The following example shows what the port of the C example function would look like.

function grl_source_query (query, options, callback)
{
  -- In this example, we are assuming query is expected to contain a
  -- suitable SQL filter
  sql_query = prepare_sql_with_custom_filter (query,
                                              options.skip,
                                              options.count);
  -- Execute the resulting SQL query, which incorporates
  -- the filter provided by the user
  results = execute_sql (sql_query);

  -- For each result obtained, invoke the user callback as usual
  count = count_results (results);

  if (count == 0) then
    -- Signal "no results"
    callback ();
  else
    -- parse_next parses the next media item in the XML
    -- and creates a key-value table with the data extracted
    media = parse_next (results)
    while (media ~= nil) do
      count -= 1
      callback (media, count)
      media = parse_next (results)
    end
  end
end
    
grl_source_resolve

Parameters: Table: a key-value table of the container to browse. Table: a key-value table with the search options. Function: a callback function with which the results should be returned.

Return value: None.

Description: Similar to its C version.

The following example shows what the port of the C example function would look like. Note that may_resolve function is handled automatically by Lua, so only the resolve function itself needs implementation.

-- In this example we assume a plugin that can resolve thumbnail
-- information for audio items given that we have artist and album
-- information available

function grl_source_resolve (media, options, callback)
  for key, value in pairs(options.requested_keys) do
    if (value == "thumbnail") then
      thumbnail_needed = true
    end
  end

  if (not thumbnail_needed) or (not media) or (not media.artist)
    or (not media.album) or (#media.artist == 0) or (#media.album == 0)
  then
    callback()
    return
  end

  media.thumbnail = {}
  media.thumbnail[1] = resolve_thumb_uri (artist, album)

  callback (media)
end
    

Configuring Lua sources

There are a few differences between how the configuration is done on C sources and how it is done in Lua sources in order to keep writing Lua sources simpler. All configuration necessary is kept in a global table named "source" and this table is used to:

  • register the Lua source in Grilo
  • request configuration from Application (username, password, etc)
  • inform what metadata this source can provide
  • inform what metadata this source needs

The example bellow is the source table from Metrolyrics. We will pass through the required and optional fields and what are the expected values from each.

source table: Registering your lua source
source = {
  id = "grl-metrolyrics",
  name = "Metrolyrics",
  description = "a source for lyrics",
  supported_keys = { "lyrics" },
  supported_media = { 'audio', 'video' },
  resolve_keys = {
    ["type"] = "audio",
    required = { "artist", "title" },
  },
  tags = { 'music', 'net:internet' },
}
    
id

Description: The source-id property.

Expecting: String

Required: Yes

name

Description: The source-name property.

Expecting: String

Required: Yes

description

Description: The source-desc property.

Expecting: String

Required: Yes

icon

Description: The source-icon property.

Expecting: an URI as String

Required: No

tags

Description: The source-tags property.

Expecting: an Array of tags

Required: No

auto_split_threshold

Description: The auto-split-threshold property.

Expecting: Number

Required: No

supported_media

Description: The supported-media property.

Expecting: Array of supported medias

Required: Yes

config_keys

Description: This table is used when source is being registered in order to use config keys from application. There are two possible values to set in this table:

  • required

    Expects an array of strings with the name of GrlConfig keys that are mandatory for this source to be available.

  • optional

    Expects an array of strings with the name of GrlConfig keys that are optional for this source.

You can check at GrlConfig for a list of available config-keys.

Expecting: Table

Required: No

supported_keys

Description: For each value of the array, we expect the name of a metadata-key that the source should support as described at supported-keys. Check here for a list of all core metadata-keys

Expecting: Array of Strings

Required: No

slow_keys

Description: For each value of the array, we expect the name of a metadata-key that the source should support as described at slow-keys. Check here for a list of all core metadata-keys

Expecting: Array of Strings

Required: No

resolve_keys

Description: This table is used for resolve operation. There are two possible values to set in this table:

  • type

    The media type that the source can resolve. Expects one of the following strings which correspond to its Grilo media type: audio, video, image, all.

  • required

    which expects an array with the name of metadata-keys necessary to the resolve operation. Check here for a list of all core metadata-keys.

Expecting: Table

Required: No

dependencies

Description: An array of dependencies that are required for this source to run. Each dependency are load using lua "require" function. If the module is not available then the source will not be registered.

Expecting: Array of Strings

Required: No

Lua-Factory library

Lua-Factory provides a Lua library to communicate with Grilo and also to provide utility functions to the Lua source. This functions are accessible through the grl global table.

Specific functions design for Lua only can be find inside grl.lua table.

It is also worth mention that it is possible to use other libraries by including them at the global source table under "dependencies" field. Lua-Factory will check if the library exists on source-initialization.

grl.get_options

Parameters: String

Expecting: String

grl.get_requested_keys

Parameters: String

Expecting: String

grl.get_media_keys

Parameters: String

Expecting: String

grl.callback

Parameters: String

Expecting: String

grl.fetch

Parameters: String

Expecting: String

grl.debug

Parameters: String

Expecting: String

grl.warning

Parameters: String

Expecting: String

grl.dgettexts

Parameters: String

Expecting: String

grl.decode

Parameters: String

Expecting: String

grl.unescape

Parameters: String

Expecting: String

grl.unzip

Parameters: String

Expecting: String