static void source_browser (gpointer data, gpointer user_data) { GrlSource *source = GRL_SOURCE (data); GrlMedia *media = GRL_MEDIA (user_data); GList *media_elements; GError *error = NULL; GList *keys; GrlOperationOptions *options = NULL; GrlCaps *caps; keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_MODIFICATION_DATE, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_CHILDCOUNT, NULL); g_debug ("Detected new source available: '%s'", grl_source_get_name (source)); if (!(grl_source_supported_operations (source) & GRL_OP_BROWSE)) goto out; g_debug ("Browsing source: %s", grl_source_get_name (source)); /* Here is how you can browse a source, you have to provide: 1) The source you want to browse contents from. 2) The container object you want to browse (NULL for the root container) 3) A list of metadata keys we are interested in. 4) Options to control certain aspects of the browse operation. 5) A callback that the framework will invoke for each available result 6) User data for the callback It returns an operation identifier that you can use to match results with the corresponding request (we ignore it here) */ caps = grl_source_get_caps (source, GRL_OP_BROWSE); options = grl_operation_options_new (caps); grl_operation_options_set_count (options, BROWSE_CHUNK_SIZE); grl_operation_options_set_resolution_flags (options, GRL_RESOLVE_IDLE_RELAY); media_elements = grl_pls_browse_sync (GRL_SOURCE (source), media, keys, options, NULL, &error); if (!media_elements) { g_debug ("No elements found for source: %s!", grl_source_get_name (source)); goto out; } if (error) g_error ("Failed to browse source: %s", error->message); g_list_foreach (media_elements, element_browser, source); out: g_list_free (keys); g_clear_object (&options); }
static void server_found_cb (GrlDleynaServersManager *serversmgr, GrlDleynaServer *server, gpointer *user_data) { GrlPlugin *plugin = GRL_PLUGIN (user_data); GrlDleynaMediaDevice *device; GrlSource *source; GrlRegistry *registry; GError *error = NULL; GRL_DEBUG (G_STRFUNC); device = grl_dleyna_server_get_media_device (server); GRL_DEBUG ("%s udn: %s ", G_STRFUNC, grl_dleyna_media_device_get_udn (device)); registry = grl_registry_get_default (); source = GRL_SOURCE (grl_dleyna_source_new (server)); GRL_DEBUG ("%s id: %s ", G_STRFUNC, grl_source_get_id (source)); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), &error); if (error != NULL) { GRL_WARNING ("Failed to register source for DLNA device %s: %s", grl_dleyna_media_device_get_udn (device), error->message); g_error_free (error); } }
static void tracker_evt_update_items_cb (gpointer key, gpointer value, tracker_evt_update_t *evt) { guint id = GPOINTER_TO_INT (key); gchar *str_id; GrlTrackerSource *source = (GrlTrackerSource *) value; GrlMedia *media; GRL_DEBUG ("%s: evt=%p", __FUNCTION__, evt); if (!source) { g_assert ("\tnot in cache ???"); return; } if (!grl_tracker_source_can_notify (source)) { GRL_DEBUG ("\tno notification for source %s...", grl_source_get_name (GRL_SOURCE (source))); return; } media = grl_media_new (); str_id = g_strdup_printf ("%i", id); grl_media_set_id (media, str_id); g_free (str_id); GRL_DEBUG ("\tNotify id=%u source=%s", id, grl_source_get_name (GRL_SOURCE (source))); grl_source_notify_change (GRL_SOURCE (source), media, evt->change_type, FALSE); g_object_unref (media); }
/** * grl_plugin_get_sources: * @plugin: a plugin * * Gets the sources belonging to @plugin. * * Returns: (transfer container) (element-type GrlSource): a #GList of * #GrlSource<!-- -->s. The content of the list should not be modified or * freed. Use g_list_free() when done using the list. * * Since: 0.2.0 **/ GList * grl_plugin_get_sources (GrlPlugin *plugin) { GrlRegistry *registry; GList *all_sources; GList *plugin_sources = NULL; GList *sources_iter; g_return_val_if_fail (GRL_IS_PLUGIN (plugin), NULL); registry = grl_registry_get_default (); all_sources = grl_registry_get_sources (registry, FALSE); for (sources_iter = all_sources; sources_iter; sources_iter = g_list_next (sources_iter)) { if (grl_source_get_plugin (GRL_SOURCE (sources_iter->data)) == plugin) { plugin_sources = g_list_prepend (plugin_sources, sources_iter->data); } } g_list_free (all_sources); return plugin_sources; }
static void on_g_volume_monitor_event (GVolumeMonitor *monitor, gpointer device, GrlOpticalMediaSource *source) { grl_source_notify_change (GRL_SOURCE (source), NULL, GRL_CONTENT_CHANGED, TRUE); }
static void update_source (MexGriloFeed *feed, GrlSource *new_source) { MexGriloFeedPrivate *priv = feed->priv; MexGriloFeedClass *klass = MEX_GRILO_FEED_GET_CLASS (feed); if (priv->source != NULL) { g_signal_handlers_disconnect_by_func (priv->source, G_CALLBACK (klass->content_updated), feed); g_object_unref (priv->source); priv->source = NULL; } if (new_source) { gchar *lower; const gchar *source_name = grl_source_get_name (GRL_SOURCE (new_source)); priv->source = g_object_ref (new_source); g_signal_connect (priv->source, "content-changed", G_CALLBACK (klass->content_updated), feed); lower = g_ascii_strdown (source_name, -1); if (strstr (lower, "removable")) { g_object_set (feed, "icon-name", "icon-panelheader-usb", NULL); } else { g_object_set (feed, "icon-name", "icon-panelheader-computer", NULL); } g_free (lower); } }
gboolean grl_gravatar_source_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GRL_LOG_DOMAIN_INIT (gravatar_log_domain, "gravatar"); GRL_DEBUG ("grl_gravatar_source_plugin_init"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); if (!GRL_METADATA_KEY_ARTIST_AVATAR && !GRL_METADATA_KEY_AUTHOR_AVATAR) { GRL_WARNING ("Unable to register \"author-avatar\" nor \"artist-avatar\""); return FALSE; } GrlGravatarSource *source = grl_gravatar_source_new (); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
static void impl_deactivate (PeasActivatable *bplugin) { RBGriloPlugin *plugin = RB_GRILO_PLUGIN (bplugin); GHashTableIter iter; gpointer key, value; g_signal_handler_disconnect (plugin->registry, plugin->handler_id_source_added); g_signal_handler_disconnect (plugin->registry, plugin->handler_id_source_removed); g_hash_table_iter_init (&iter, plugin->sources); while (g_hash_table_iter_next (&iter, &key, &value)) { grl_registry_unregister_source (plugin->registry, GRL_SOURCE (key), NULL); rb_display_page_delete_thyself (RB_DISPLAY_PAGE (value)); } g_hash_table_destroy (plugin->sources); plugin->sources = NULL; plugin->registry = NULL; if (plugin->emit_cover_art_id != 0) { g_source_remove (plugin->emit_cover_art_id); plugin->emit_cover_art_id = 0; } g_signal_handlers_disconnect_by_func (plugin->shell_player, G_CALLBACK (playing_song_changed_cb), plugin); g_object_unref (plugin->shell_player); plugin->shell_player = NULL; g_object_unref (plugin->art_store); plugin->art_store = NULL; }
static void grl_vimeo_source_init (GrlVimeoSource *source) { source->priv = grl_vimeo_source_get_instance_private (source); grl_source_set_auto_split_threshold (GRL_SOURCE (source), MAX_ELEMENTS); }
static void grl_vimeo_source_init (GrlVimeoSource *source) { source->priv = GRL_VIMEO_SOURCE_GET_PRIVATE (source); grl_source_set_auto_split_threshold (GRL_SOURCE (source), MAX_ELEMENTS); }
gboolean grl_tmdb_source_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GrlConfig *config; char *api_key; GRL_LOG_DOMAIN_INIT (tmdb_log_domain, "tmdb"); GRL_DEBUG ("grl_tmdb_source_plugin_init"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); if (!configs) { GRL_INFO ("No configuration provided. Will not load plugin"); return FALSE; } 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; } GRL_TMDB_METADATA_KEY_BACKDROP = register_metadata_key (registry, "tmdb-backdrop", "tmdb-backdrop", "A list of URLs for movie backdrops"); GRL_TMDB_METADATA_KEY_POSTER = register_metadata_key (registry, "tmdb-poster", "tmdb-poster", "A list of URLs for movie posters"); GRL_TMDB_METADATA_KEY_IMDB_ID = register_metadata_key (registry, "tmdb-imdb-id", "tmdb-imdb-id", "ID of this movie at imdb.org"); GRL_TMDB_METADATA_KEY_TMDB_ID = register_metadata_key (registry, "tmdb-id", "tmdb-id", "ID of this movie at tmdb.org"); GrlTmdbSource *source = grl_tmdb_source_new (api_key); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); g_free (api_key); return TRUE; }
void test_setup_thetvdb (void) { GrlConfig *config; GrlRegistry *registry; GError *error = NULL; if (tmp_dir == NULL) { /* Only create tmp dir and set the XDG_DATA_HOME once */ tmp_dir = g_build_filename (g_get_tmp_dir (), "test-thetvdb-XXXXXX", NULL); tmp_dir = g_mkdtemp (tmp_dir); g_assert_nonnull (tmp_dir); g_setenv ("XDG_DATA_HOME", tmp_dir, TRUE); } config = grl_config_new (THETVDB_ID, NULL); grl_config_set_api_key (config, "THETVDB_TEST_MOCK_API_KEY"); registry = grl_registry_get_default (); grl_registry_add_config (registry, config, &error); g_assert_no_error (error); grl_registry_load_plugin_by_id (registry, THETVDB_ID, &error); g_assert_no_error (error); source = GRL_SOURCE (grl_registry_lookup_source (registry, THETVDB_ID)); g_assert (source != NULL); g_assert (grl_source_supported_operations (source) & GRL_OP_RESOLVE); }
static void grl_bliptv_source_init (GrlBliptvSource *self) { self->priv = BLIPTV_SOURCE_PRIVATE (self); self->priv->wc = grl_net_wc_new (); grl_source_set_auto_split_threshold (GRL_SOURCE (self), MAX_ELEMENTS); }
static void grl_dpap_service_removed_cb (DMAPMdnsBrowser *browser, const gchar *service_name, GrlPlugin *plugin) { GrlRegistry *registry = grl_registry_get_default (); GrlDpapSource *source = g_hash_table_lookup (sources, service_name); GRL_DEBUG (__FUNCTION__); if (source) { grl_registry_unregister_source (registry, GRL_SOURCE (source), NULL); g_hash_table_remove (sources, service_name); } }
static void tracker_evt_update_source_del (tracker_evt_update_t *evt, GrlTrackerSource *source) { GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); priv->notification_ref++; priv->state = GRL_TRACKER_SOURCE_STATE_DELETING; evt->old_sources = g_list_append (evt->old_sources, source); GRL_DEBUG ("Predel source p=%p name=%s id=%s count=%u", source, grl_source_get_name (GRL_SOURCE (source)), grl_tracker_source_get_tracker_source (source), priv->notification_ref); }
static void parsed_finished (TotemPlParser *pl, GAsyncResult *result, BrowseData *data) { TotemPlParserResult retval; GError *error = NULL; retval = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (pl), result, &error); /* Do the fallback ourselves */ if (retval == TOTEM_PL_PARSER_RESULT_IGNORED) { GRL_DEBUG ("%s: Falling back for %s as has it's been ignored", __FUNCTION__, grl_media_get_id (data->media)); grl_media_set_url (data->media, grl_media_get_id (data->media)); retval = TOTEM_PL_PARSER_RESULT_SUCCESS; } if (retval == TOTEM_PL_PARSER_RESULT_SUCCESS && grl_media_get_url (data->media) != NULL) { GrlOpticalMediaSource *source; source = GRL_OPTICAL_MEDIA_SOURCE (data->bs->source); GRL_DEBUG ("%s: Adding %s which resolved to %s", __FUNCTION__, grl_media_get_id (data->media), grl_media_get_url (data->media)); data->bs->callback (GRL_SOURCE (source), data->bs->operation_id, data->media, -1, data->bs->user_data, NULL); source->priv->list = g_list_append (source->priv->list, g_object_ref (data->media)); } else { if (retval == TOTEM_PL_PARSER_RESULT_ERROR || retval == TOTEM_PL_PARSER_RESULT_CANCELLED) { GRL_WARNING ("Failed to parse '%s': %s", grl_media_get_id (data->media), error ? error->message : "No reason"); g_error_free (error); } g_object_unref (data->media); } data->media = NULL; resolve_disc_urls (data); }
static void on_g_volume_monitor_changed_event (GVolumeMonitor *monitor, GMount *mount, GrlOpticalMediaSource *source) { GList *l; l = g_list_find_custom (source->priv->list, mount, find_mount); if (!l) return; media_set_metadata (mount, l->data); if (source->priv->notify_changes) { grl_source_notify_change (GRL_SOURCE (source), l->data, GRL_CONTENT_CHANGED, FALSE); } }
static gboolean grl_magnatune_plugin_init(GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GrlMagnatuneSource *source; GRL_LOG_DOMAIN_INIT(magnatune_log_domain, "magnatune"); GRL_DEBUG("magnatune_plugin_init"); source = grl_magnatune_source_new(); grl_registry_register_source(registry, plugin, GRL_SOURCE(source), NULL); return TRUE; }
gboolean grl_raitv_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GRL_LOG_DOMAIN_INIT (raitv_log_domain, "raitv"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); GrlRaitvSource *source = grl_raitv_source_new (); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
static void mex_grilo_feed_constructed (GObject *object) { const gchar *title; MexGriloFeed *self = (MexGriloFeed *) object; MexGriloFeedPrivate *priv = self->priv; MexGriloFeedClass *klass = MEX_GRILO_FEED_GET_CLASS (object); if (G_OBJECT_CLASS (mex_grilo_feed_parent_class)->constructed) G_OBJECT_CLASS (mex_grilo_feed_parent_class)->constructed (object); if (priv->source == NULL) { g_warning ("No source supplied"); return; } /* Fill keys in case it's not already done at creation. */ if (priv->query_keys == NULL) { priv->query_keys = mex_grilo_program_get_default_keys (); } if (priv->metadata_keys == NULL) { priv->metadata_keys = g_list_copy (priv->query_keys); } title = NULL; if (priv->root) title = grl_media_get_title (priv->root); if (!title && GRL_IS_SOURCE (priv->source)) title = grl_source_get_name (GRL_SOURCE (priv->source)); if (title) g_object_set (object, "title", title, NULL); if (priv->source != NULL) { g_signal_handlers_disconnect_by_func (priv->source, G_CALLBACK (klass->content_updated), self); g_signal_connect (priv->source, "content-changed", G_CALLBACK (klass->content_updated), self); } }
static void on_g_volume_monitor_removed_event (GVolumeMonitor *monitor, GMount *mount, GrlOpticalMediaSource *source) { GList *l; GrlMedia *media; l = g_list_find_custom (source->priv->list, mount, find_mount); if (!l) return; media = l->data; source->priv->list = g_list_remove (source->priv->list, media); if (source->priv->notify_changes) { grl_source_notify_change (GRL_SOURCE (source), media, GRL_CONTENT_REMOVED, FALSE); } g_object_unref (media); }
static void grl_dpap_service_added_cb (DMAPMdnsBrowser *browser, DMAPMdnsBrowserService *service, GrlPlugin *plugin) { GrlRegistry *registry = grl_registry_get_default (); GrlDpapSource *source = grl_dpap_source_new (service); GRL_DEBUG (__FUNCTION__); g_object_add_weak_pointer (G_OBJECT (source), (gpointer *) &source); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); if (source != NULL) { g_hash_table_insert (sources, g_strdup (service->name), g_object_ref (source)); g_object_remove_weak_pointer (G_OBJECT (source), (gpointer *) &source); } }
static void resolve_closure_callback (ResolveClosure *closure, const GError *outer_error) { GError *error = NULL; if (outer_error && outer_error->domain != GRL_CORE_ERROR) { error = g_error_new_literal (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED, outer_error->message); } closure->rs->callback (GRL_SOURCE (closure->self), closure->rs->operation_id, closure->rs->media, closure->rs->user_data, error); if (error) g_error_free (error); }
gboolean grl_metadata_store_source_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GRL_LOG_DOMAIN_INIT (metadata_store_log_domain, "metadata-store"); GRL_DEBUG ("grl_metadata_store_source_plugin_init"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); GrlMetadataStoreSource *source = grl_metadata_store_source_new (); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
static void parsed_finished_item (TotemPlParser *pl, GAsyncResult *result, GrlOpticalMediaSource *source) { GrlMedia **media; TotemPlParserResult retval; media = g_object_get_data (G_OBJECT (pl), "media"); retval = totem_pl_parser_parse_finish (TOTEM_PL_PARSER (pl), result, NULL); if (retval == TOTEM_PL_PARSER_RESULT_SUCCESS && grl_media_get_url (*media) != NULL) { source->priv->list = g_list_append (source->priv->list, g_object_ref (*media)); if (source->priv->notify_changes) { grl_source_notify_change (GRL_SOURCE (source), *media, GRL_CONTENT_ADDED, FALSE); } } g_object_unref (*media); g_object_unref (pl); }
static void mex_bliptv_plugin_init (MexBliptvPlugin *self) { MexBliptvPluginPrivate *priv; GrlRegistry *registry; GList *sources, *iter; priv = self->priv = GET_PRIVATE (self); priv->video_models = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); priv->query_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID, GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_PUBLICATION_DATE, NULL); priv->video_keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID, GRL_METADATA_KEY_DESCRIPTION, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_THUMBNAIL, GRL_METADATA_KEY_WIDTH, GRL_METADATA_KEY_HEIGHT, NULL); priv->manager = mex_model_manager_get_default (); registry = grl_registry_get_default (); sources = grl_registry_get_sources (registry, FALSE); for (iter = sources; iter != NULL; iter = iter->next) handle_new_source (self, GRL_SOURCE (iter->data)); g_list_free (sources); g_signal_connect (registry, "source-added", G_CALLBACK (registry_source_added_cb), self); g_signal_connect (registry, "source-removed", G_CALLBACK (registry_source_removed_cb), self); }
static gboolean grl_bookmarks_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GParamSpec *spec; GRL_LOG_DOMAIN_INIT (bookmarks_log_domain, "bookmarks"); GRL_DEBUG ("grl_bookmarks_plugin_init"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); spec = g_param_spec_boxed ("bookmark-date", "Bookmark date", "When the media was bookmarked", G_TYPE_DATE_TIME, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE), GRL_BOOKMARKS_KEY_BOOKMARK_TIME = grl_registry_register_metadata_key (registry, spec, NULL); /* If key was not registered, could be that it is already registered. If so, check if type is the expected one, and reuse it */ if (GRL_BOOKMARKS_KEY_BOOKMARK_TIME == GRL_METADATA_KEY_INVALID) { g_param_spec_unref (spec); GRL_BOOKMARKS_KEY_BOOKMARK_TIME = grl_registry_lookup_metadata_key (registry, "bookmark-date"); if (grl_metadata_key_get_type (GRL_BOOKMARKS_KEY_BOOKMARK_TIME) != G_TYPE_DATE_TIME) { GRL_BOOKMARKS_KEY_BOOKMARK_TIME = GRL_METADATA_KEY_INVALID; } } GrlBookmarksSource *source = grl_bookmarks_source_new (); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
gboolean grl_optical_media_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { GRL_LOG_DOMAIN_INIT (optical_media_log_domain, "optical_media"); GRL_DEBUG ("%s", __FUNCTION__); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); GrlOpticalMediaSource *source = grl_optical_media_source_new (); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
gboolean grl_local_metadata_source_plugin_init (GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { guint config_count; gboolean guess_video = TRUE; GrlConfig *config; GRL_LOG_DOMAIN_INIT (local_metadata_log_domain, "local-metadata"); GRL_DEBUG ("grl_local_metadata_source_plugin_init"); /* Initialize i18n */ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); if (!configs) { GRL_INFO ("\tConfiguration not provided! Using default configuration."); } else { config_count = g_list_length (configs); if (config_count > 1) { GRL_INFO ("\tProvided %i configs, but will only use one", config_count); } config = GRL_CONFIG (configs->data); guess_video = grl_config_get_boolean (config, "guess-video"); } GrlLocalMetadataSource *source = grl_local_metadata_source_new (guess_video); grl_registry_register_source (registry, plugin, GRL_SOURCE (source), NULL); return TRUE; }
void test_setup_tmdb (void) { GrlConfig *config; GrlRegistry *registry; GError *error = NULL; config = grl_config_new (TMDB_PLUGIN_ID, NULL); /* It does not matter what we set this to. It just needs to be non-empty because we're * going to fake the network responses. */ grl_config_set_api_key (config, "TMDB_TEST_API_KEY"); registry = grl_registry_get_default (); grl_registry_add_config (registry, config, &error); g_assert_no_error (error); grl_registry_load_plugin_by_id (registry, TMDB_PLUGIN_ID, &error); g_assert_no_error (error); source = GRL_SOURCE (grl_registry_lookup_source (registry, TMDB_PLUGIN_ID)); g_assert (source != NULL); g_assert (grl_source_supported_operations (source) & GRL_OP_RESOLVE); }