Esempio n. 1
0
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
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);
}
Esempio n. 3
0
static void
grilo_source_added_cb (GrlRegistry *registry, GrlSource *grilo_source, RBGriloPlugin *plugin)
{
	GrlPlugin *grilo_plugin;
	GrlSupportedOps ops;
	const GList *keys;
	RBSource *source;
	RBShell *shell;
	int i;

	if (!(grl_source_get_supported_media (grilo_source) & GRL_MEDIA_TYPE_AUDIO)) {
		rb_debug ("grilo source %s doesn't support audio",
			  grl_source_get_name (grilo_source));
		goto ignore;
	}

	grilo_plugin = grl_source_get_plugin (grilo_source);
	for (i = 0; i < G_N_ELEMENTS (ignored_plugins); i++) {
		if (g_str_equal (ignored_plugins[i], grl_plugin_get_id (grilo_plugin))) {
			rb_debug ("grilo source %s is blacklisted",
				  grl_source_get_name (grilo_source));
			goto ignore;
		}
	}

	ops = grl_source_supported_operations (grilo_source);
	if (((ops & GRL_OP_BROWSE) == 0) && ((ops & GRL_OP_SEARCH) == 0)) {
		rb_debug ("grilo source %s is not interesting",
			  grl_source_get_name (grilo_source));
		goto ignore;
	}

	keys = grl_source_supported_keys (grilo_source);
	if (g_list_find ((GList *)keys, GINT_TO_POINTER (GRL_METADATA_KEY_URL)) == NULL) {
		rb_debug ("grilo source %s doesn't do urls", grl_source_get_name (grilo_source));
		goto ignore;
	}

	rb_debug ("new grilo source: %s", grl_source_get_name (grilo_source));

	source = rb_grilo_source_new (G_OBJECT (plugin), grilo_source);
	g_hash_table_insert (plugin->sources, g_object_ref (grilo_source), g_object_ref_sink (source));

	/* probably put some sources under 'shared', some under 'stores'? */
	g_object_get (plugin, "object", &shell, NULL);
	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_SHARED);
	g_object_unref (shell);

	return;

ignore:
	grl_registry_unregister_source (registry, grilo_source, NULL);
}
static void
media_browse_next (RBGriloSource *source)
{
	GrlOperationOptions *options;

	rb_debug ("next media_browse op for %s (%d)",
		  grl_source_get_name (source->priv->grilo_source),
		  source->priv->media_browse_position);

	source->priv->media_browse_got_results = FALSE;
	if (source->priv->media_browse_container != NULL) {
		options = make_operation_options (source,
						  GRL_OP_BROWSE,
						  source->priv->media_browse_position);
		source->priv->media_browse_op =
			grl_source_browse (source->priv->grilo_source,
					   source->priv->media_browse_container,
					   source->priv->grilo_keys,
					   options,
					   (GrlSourceResultCb) grilo_media_browse_cb,
					   source);
	} else {
		options = make_operation_options (source,
						  GRL_OP_SEARCH,
						  source->priv->media_browse_position);
		source->priv->media_browse_op =
			grl_source_search (source->priv->grilo_source,
					   source->priv->search_text,
					   source->priv->grilo_keys,
					   options,
					   (GrlSourceResultCb) grilo_media_browse_cb,
					   source);
	}
}
static void
start_media_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, guint limit)
{
	rb_debug ("starting media browse for %s",
		  grl_source_get_name (source->priv->grilo_source));

	/* cancel existing operation? */
	if (source->priv->media_browse_op != 0) {
		grl_operation_cancel (source->priv->media_browse_op);
	}

	if (source->priv->media_browse_container != NULL) {
		g_object_unref (source->priv->media_browse_container);
	}
	source->priv->media_browse_container = container;
	if (container_iter != NULL) {
		/* hrm, probably have to use row references here.. */
		source->priv->media_browse_container_iter = *container_iter;
	}
	source->priv->media_browse_position = 0;
	source->priv->media_browse_limit = limit;
	source->priv->media_browse_got_containers = FALSE;

	if (source->priv->query_model != NULL) {
		g_object_unref (source->priv->query_model);
	}
	source->priv->query_model = rhythmdb_query_model_new_empty (source->priv->db);
	rb_entry_view_set_model (RB_ENTRY_VIEW (source->priv->entry_view), source->priv->query_model);
	g_object_set (source, "query-model", source->priv->query_model, NULL);

	media_browse_next (source);
}
/* Callback invoked whenever a source goes away */
static void
source_removed_cb (GrlRegistry *registry,
                   GrlSource *source,
                   gpointer user_data)
{
  GList *entry;
  const gchar *source_name;
  gchar *source_id;

  source_name =
    grl_source_get_name (source);
  source_id =
    g_strdup (grl_source_get_id (source));

  if (!dups) {
    entry = g_list_find_custom (providers_names,
                                source_name,
                                (GCompareFunc) g_strcmp0);
    if (entry) {
      g_free (entry->data);
      providers_names = g_list_delete_link (providers_names, entry);
    }
  }

  sanitize (source_id);
  g_hash_table_remove (servers, source_id);
  g_free (source_id);
}
Esempio n. 7
0
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);
  }
}
Esempio n. 8
0
static void
source_added_cb (GrlRegistry *registry, GrlSource *source, gpointer user_data)
{
  g_debug ("Detected new source available: '%s'",
           grl_source_get_name (source));

  /* Usually you may add the new service to the user interface so the user
     can interact with it (browse, search, etc) */
}
Esempio n. 9
0
static void
source_removed_cb (GrlRegistry *registry, GrlSource *source, gpointer user_data)
{
  g_debug ("Source '%s' is gone",
           grl_source_get_name (source));

  /* Usually you would inform the user that this service is no longer
     available (for example a UPnP server was shutdown) and remove it
     from the user interface. */
}
static void
browse_next (RBGriloSource *source)
{
	GrlOperationOptions *options;
	rb_debug ("next browse op for %s (%d)",
		  grl_source_get_name (source->priv->grilo_source),
		  source->priv->browse_position);
	source->priv->browse_got_results = FALSE;
	options = make_operation_options (source, GRL_OP_BROWSE, source->priv->browse_position);
	source->priv->browse_op = grl_source_browse (source->priv->grilo_source,
						     source->priv->browse_container,
						     source->priv->grilo_keys,
						     options,
						     (GrlSourceResultCb) grilo_browse_cb,
						     source);
}
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);
}
Esempio n. 12
0
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);
  }
}
Esempio n. 13
0
RBSource *
rb_grilo_source_new (GObject *plugin, GrlSource *grilo_source)
{
	GObject *source;
	RBShell *shell;
	GSettings *settings;
	RhythmDBEntryType *entry_type;
	RhythmDB *db;
	char *name;

	name = g_strdup_printf ("grilo:%s", grl_source_get_id (grilo_source));

	g_object_get (plugin, "object", &shell, NULL);
	g_object_get (shell, "db", &db, NULL);
	entry_type = g_object_new (rb_grilo_entry_type_get_type (),
				   "db", db,
				   "name", name,
				   "save-to-disk", FALSE,
				   "category", RHYTHMDB_ENTRY_NORMAL,
				   "type-data-size", sizeof(RBGriloEntryData),
				   NULL);
	rhythmdb_register_entry_type (db, entry_type);
	g_object_unref (db);
	g_free (name);

	settings = g_settings_new ("org.gnome.rhythmbox.plugins.grilo");
	source = g_object_new (RB_TYPE_GRILO_SOURCE,
			       "name", grl_source_get_name (grilo_source),
			       "entry-type", entry_type,
			       "shell", shell,
			       "plugin", plugin,
			       "show-browser", FALSE,
			       "settings", g_settings_get_child (settings, "source"),
			       "grilo-source", grilo_source,
			       NULL);
	g_object_unref (settings);
	rb_display_page_set_icon_name (RB_DISPLAY_PAGE (source), "network-server-symbolic");

	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);

	g_object_unref (shell);
	return RB_SOURCE (source);
}
static void
start_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, int position)
{
	rb_debug ("starting browse op for %s", grl_source_get_name (source->priv->grilo_source));

	/* cancel existing operation? */
	if (source->priv->browse_op != 0) {
		grl_operation_cancel (source->priv->browse_op);
	}

	if (source->priv->browse_container != NULL) {
		g_object_unref (source->priv->browse_container);
	}
	source->priv->browse_container = container;
	if (container_iter != NULL) {
		/* hrm, probably have to use row references here.. */
		source->priv->browse_container_iter = *container_iter;
	}
	source->priv->browse_position = position;
	source->priv->browse_got_media = FALSE;

	browse_next (source);
}
static void
resolve_cb (GrlSource *source,
            guint operation_id,
            GrlMedia *media,
            gpointer user_data,
            const GError *error)
{
  GriloMs2Data *grdata = (GriloMs2Data *) user_data;

  if (error) {
    grdata->error = g_error_copy (error);
    grdata->updated = TRUE;
    return;
  }

  /* Special case: for root media, if there is no title use the source's name */
  if (grl_media_get_id (media) == NULL &&
      !grl_data_has_key (GRL_DATA (media), GRL_METADATA_KEY_TITLE)) {
    grl_media_set_title (media,
                         grl_source_get_name (source));
  }

  grdata->properties = ms2_server_new_properties_hashtable ();
  fill_properties_table (grdata->server,
                         grdata->properties,
                         grdata->keys,
                         media);

  fill_other_properties_table (grdata->server,
                               source,
                               grdata->properties,
                               grdata->other_keys,
                               media);

  grdata->updated = TRUE;
}
Esempio n. 16
0
static void
browse_cb (GrlSource    *source,
           guint         browse_id,
           GrlMedia     *media,
           guint         remaining,
           gpointer      userdata,
           const GError *error)
{
  MexGriloFeed *feed = (MexGriloFeed *) userdata;
  MexGriloFeedPrivate *priv = feed->priv;
  MexGriloProgram *program;

  if (error) {
    g_warning ("Error browsing: %s", error->message);
    return;
  }

  if (priv->op == NULL) {
    g_warning ("No operation found");
    return;
  }

  if (priv->op->op_id != browse_id)
    return;

  if (media) {
    /*
     * FIXME: talk to thomas/lionel/grilo guys about that crasher. We are
     * being called with what seems to be an invalid media when cancelled.
     * this is obviously temporary to enable people to work in the meantime
     */
    gconstpointer foo = grl_media_get_id (media);
    if (!foo) {
      const gchar *source_name;

      source_name =
        grl_source_get_name (GRL_SOURCE (priv->source));
      g_warning ("FIXME: oh no, a grilo bug! (on the '%s' source)",
                 source_name);
      return;
    }

    program = MEX_GRILO_PROGRAM (mex_feed_lookup (MEX_FEED (feed),
                                                  grl_media_get_id (media)));
    if (program != NULL) {
      mex_grilo_program_set_grilo_media (program, media);
      return;
    } else {
      emit_media_added (feed, media);
      g_object_unref (media);
    }
  }

  priv->op->count++;

  if (remaining == 0) {
    priv->op->op_id = 0;

    /* Emit completed signal */
    priv->completed = TRUE;
    g_object_notify (G_OBJECT (feed), "completed");
  }
}
Esempio n. 17
0
static struct MultipleSearchData *
start_multiple_search_operation (guint search_id,
				 const GList *sources,
				 const gchar *text,
				 const GList *keys,
				 const GList *skip_counts,
				 gint count,
				 GrlOperationOptions *options,
				 GrlSourceResultCb user_callback,
				 gpointer user_data)
{
  GRL_DEBUG ("start_multiple_search_operation");

  struct MultipleSearchData *msd;
  GList *iter_sources, *iter_skips;
  guint n;
  gint first_count, individual_count;

  /* Prepare data required to execute the operation */
  msd = g_new0 (struct MultipleSearchData, 1);
  msd->table = g_hash_table_new_full (g_direct_hash, g_direct_equal,
				      NULL, g_free);
  msd->remaining =
      (count == GRL_COUNT_INFINITY) ? GRL_COUNT_INFINITY : (count - 1);
  msd->search_id = search_id;
  msd->text = g_strdup (text);
  msd->keys = g_list_copy ((GList *) keys);
  msd->options = g_object_ref (options);
  msd->user_callback = user_callback;
  msd->user_data = user_data;

  /* Compute the # of items to request by each source */
  n = g_list_length ((GList *) sources);
  if (count == GRL_COUNT_INFINITY) {
    individual_count = GRL_COUNT_INFINITY;
    first_count = GRL_COUNT_INFINITY;
  } else {
    individual_count = count / n;
    first_count = individual_count + count % n;
  }

  /* Issue search operations on each source */
  iter_sources = (GList *) sources;
  iter_skips = (GList *) skip_counts;
  n = 0;
  while (iter_sources) {
    GrlSource *source;
    guint c, id;
    struct ResultCount *rc;
    guint skip;

    source = GRL_SOURCE (iter_sources->data);

    /* c is the count to use for this source */
    c = (n == 0) ? first_count : individual_count;
    n++;

    /* Only interested in sources with c != 0 */
    if (c != 0) {
      GrlOperationOptions *source_options = NULL;
      GrlCaps *source_caps;

      /* We use ResultCount to keep track of results emitted by this source */
      rc = g_new0 (struct ResultCount, 1);
      rc->count = c;
      g_hash_table_insert (msd->table, source, rc);

      /* Check if we have to apply a "skip" parameter to this source
	 (useful when we are chaining queries to complete the result count) */
      if (iter_skips) {
	skip = GPOINTER_TO_INT (iter_skips->data);
      } else {
	skip = 0;
      }

      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_source_search (source,
				    msd->text,
				    msd->keys,
				    source_options,
				    multiple_search_cb,
				    msd);

      GRL_DEBUG ("Operation %s:%u: Searching %u items from offset %u",
                 grl_source_get_name (GRL_SOURCE (source)),
                 id, rc->count, skip);

      g_object_unref (source_options);

      /* Keep track of this operation and this source */
      msd->search_ids = g_list_prepend (msd->search_ids, GINT_TO_POINTER (id));
      msd->sources = g_list_prepend (msd->sources, source);
      msd->sources_count++;
    }

    /* Move to the next source */
    iter_sources = g_list_next (iter_sources);
    iter_skips = g_list_next (iter_skips);
  }
static void
tracker_evt_update_orphans (tracker_evt_update_t *evt)
{
  gboolean first = TRUE;
  GString *request_str;
  GList *subject, *subjects;
  GList *source, *sources;

  GRL_DEBUG ("%s: evt=%p", __FUNCTION__, evt);

  if (g_hash_table_size (evt->orphan_items) < 1) {
    tracker_evt_postupdate_sources (evt);
    return;
  }

  sources = grl_registry_get_sources (grl_registry_get_default (),
                                      FALSE);

  request_str = g_string_new ("");
  subjects = g_hash_table_get_keys (evt->orphan_items);

  subject = subjects;
  while (subject != NULL) {
    guint id = GPOINTER_TO_INT (subject->data);
    if (GPOINTER_TO_INT (g_hash_table_lookup (evt->orphan_items,
                                              subject->data)) != GRL_CONTENT_REMOVED) {
      if (first) {
        g_string_append_printf (request_str, "%u", id);
        first = FALSE;
      }
      else {
        g_string_append_printf (request_str, ", %u", id);
      }
    } else {
      /* Notify all sources that a media been removed */
      source = sources;
      while (source != NULL) {
        if (GRL_IS_TRACKER_SOURCE (source->data)) {
          GRL_DEBUG ("\tNotify id=%u source=%s p=%p", id,
                     grl_source_get_name (GRL_SOURCE (source->data)),
                     source->data);
          if (grl_tracker_source_can_notify (GRL_TRACKER_SOURCE (source->data))) {
            GrlMedia *media = grl_media_new ();
            gchar *str_id = g_strdup_printf ("%u", id);

            grl_media_set_id (media, str_id);
            g_free (str_id);

            grl_source_notify_change (GRL_SOURCE (source->data),
                                      media,
                                      GRL_CONTENT_REMOVED,
                                      FALSE);
            g_object_unref (media);
          }
        }
        source = source->next;
      }
    }
    subject = subject->next;
  }

  g_list_free (subjects);

  if (request_str->len > 0) {
    gchar *sparql_final = g_strdup_printf (TRACKER_MEDIA_ITEM, request_str->str);

    GRL_DEBUG ("\trequest : '%s'", sparql_final);

    tracker_sparql_connection_query_async (grl_tracker_connection,
                                           sparql_final,
                                           NULL,
                                           (GAsyncReadyCallback) tracker_evt_update_orphans_cb,
                                           evt);

    g_free (sparql_final);
  } else {
    tracker_evt_postupdate_sources (evt);
  }

  g_string_free (request_str, TRUE);
}
static void
grilo_media_browse_cb (GrlSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
{
	if (operation_id != source->priv->media_browse_op) {
		return;
	}

	if (error != NULL) {
		/* do something? */
		rb_debug ("got error for %s: %s",
			  grl_source_get_name (grilo_source),
			  error->message);
		return;
	}

	GDK_THREADS_ENTER ();
	if (media != NULL) {
		source->priv->media_browse_got_results = TRUE;
		source->priv->media_browse_position++;

		if (GRL_IS_MEDIA_AUDIO (media)) {
			RhythmDBEntry *entry;
			entry = create_entry_for_media (source->priv->db,
							source->priv->entry_type,
							GRL_DATA (media),
							GRL_DATA (source->priv->browse_container));
			if (entry != NULL) {
				rhythmdb_query_model_add_entry (source->priv->query_model, entry, -1);
			}
		} else if (GRL_IS_MEDIA_BOX (media)) {
			source->priv->media_browse_got_containers = TRUE;
		}
	}

	if (remaining == 0) {
		source->priv->media_browse_op = 0;

		if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL) == 0 &&
		    source->priv->media_browse_position >= CONTAINER_GIVE_UP_POINT) {
			/* if we don't find any media within the first 100 or so results,
			 * assume this container doesn't have any.  otherwise we'd load
			 * the entire thing when it got selected, which would suck.
			 */
			rb_debug ("didn't find any media in %s, giving up", grl_media_get_title (source->priv->media_browse_container));
			gtk_tree_store_set (source->priv->browser_model,
					    &source->priv->media_browse_container_iter,
					    2, CONTAINER_NO_MEDIA,
					    -1);
		} else if (source->priv->media_browse_got_results) {
			if (source->priv->media_browse_position < source->priv->media_browse_limit) {
				media_browse_next (source);
			} else {
				char *text;

				text = g_strdup_printf (ngettext ("Only showing %d result",
								  "Only showing %d results",
								  source->priv->media_browse_position),
							source->priv->media_browse_position);
				gtk_label_set_text (GTK_LABEL (source->priv->info_bar_label), text);
				g_free (text);

				gtk_widget_show (source->priv->info_bar);
			}
		} else if (source->priv->media_browse_got_containers == FALSE &&
			   source->priv->media_browse_container != NULL) {
			/* make sure there's no marker row for this container */
			delete_marker_row (source, &source->priv->media_browse_container_iter);
		}
	}
	GDK_THREADS_LEAVE ();
}
static void
grilo_browse_cb (GrlSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
{
	if (operation_id != source->priv->browse_op) {
		return;
	}

	if (error != NULL) {
		/* do something? */
		rb_debug ("got error for %s: %s", grl_source_get_name (grilo_source), error->message);
		source->priv->browse_op = 0;
		return;
	}

	if (media != NULL) {
		source->priv->browse_got_results = TRUE;
		source->priv->browse_position++;
	}

	if (media && GRL_IS_MEDIA_BOX (media)) {

		GtkTreeIter new_row;
		if (source->priv->browse_container == NULL) {
			/* insert at the end */
			gtk_tree_store_insert_with_values (source->priv->browser_model,
							   &new_row,
							   NULL,
							   -1,
							   0, g_object_ref (media),
							   1, grl_media_get_title (media),
							   2, CONTAINER_UNKNOWN_MEDIA,
							   3, 0,
							   -1);
		} else {
			int n;
			/* insert before the expand marker row */
			n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
							    &source->priv->browse_container_iter);
			gtk_tree_store_insert_with_values (source->priv->browser_model,
							   &new_row,
							   &source->priv->browse_container_iter,
							   n - 1,
							   0, g_object_ref (media),
							   1, grl_media_get_title (media),
							   2, CONTAINER_UNKNOWN_MEDIA,
							   3, 0,
							   -1);
		}

		/* and insert an expand marker below it too */
		gtk_tree_store_insert_with_values (source->priv->browser_model,
						   NULL,
						   &new_row,
						   -1,
						   0, NULL,
						   1, "...",	/* needs to be translatable? */
						   2, CONTAINER_NO_MEDIA,
						   3, 0,
						   -1);
	} else if (media && GRL_IS_MEDIA_AUDIO (media)) {
		source->priv->browse_got_media = TRUE;
	}

	if (remaining == 0) {
		source->priv->browse_op = 0;
		if (source->priv->browse_got_results == FALSE &&
		    source->priv->browse_container != NULL) {
			/* no more results for this container, so delete the marker row */
			delete_marker_row (source, &source->priv->browse_container_iter);

			set_container_type (source, &source->priv->browse_container_iter, source->priv->browse_got_media);
			gtk_tree_store_set (source->priv->browser_model,
					    &source->priv->browse_container_iter,
					    3, -1,
					    -1);
		} else if (source->priv->browse_container != NULL) {
			if (source->priv->browse_position >= CONTAINER_GIVE_UP_POINT &&
			    gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
							    &source->priv->browse_container_iter) == 1) {
				/* no containers yet, so remove the marker row */
				delete_marker_row (source, &source->priv->browse_container_iter);
			} else {
				/* store browse position for next time we want more */
				gtk_tree_store_set (source->priv->browser_model,
						    &source->priv->browse_container_iter,
						    3, source->priv->browse_position,
						    -1);
				maybe_expand_container (source);
			}

		} else if (source->priv->browse_got_results && source->priv->browse_container == NULL) {
			/* get all top-level containers */
			browse_next (source);
		}
	}
}
static void
tracker_evt_update_orphan_item_cb (GObject              *object,
                                   GAsyncResult         *result,
                                   tracker_evt_update_t *evt)
{
  guint id;
  const gchar *type, *datasource;
  GrlTrackerSource *source = NULL;
  GError *error = NULL;

  GRL_DEBUG ("%s: evt=%p", __FUNCTION__, evt);

  if (!tracker_sparql_cursor_next_finish (evt->cursor, result, &error)) {
    if (error != NULL) {
      GRL_DEBUG ("\terror in parsing : %s", error->message);
      g_error_free (error);
    } else {
      GRL_DEBUG ("\tend of parsing...");
    }

    g_clear_object (&evt->cursor);

    if (grl_tracker_per_device_source) {
      /* Once all items have been processed, add new sources and we're
	 done. */
      tracker_evt_postupdate_sources (evt);
    } else {
      tracker_evt_update_free (evt);
    }

    return;
  }

  type = tracker_sparql_cursor_get_string (evt->cursor, 0, NULL);
  id = tracker_sparql_cursor_get_integer (evt->cursor, 1);
  datasource = tracker_sparql_cursor_get_string (evt->cursor, 2, NULL);

  GRL_DEBUG ("\tOrphan item: id=%u datasource=%s", id, datasource);

  if (!grl_tracker_per_device_source)
    source = grl_tracker_source_find ("");

  if (!source && datasource)
    source = grl_tracker_source_find (datasource);

  if (source && GRL_IS_TRACKER_SOURCE (source)) {
    GrlMedia *media;

    GRL_DEBUG (" \tAdding to cache id=%u", id);
    grl_tracker_source_cache_add_item (grl_tracker_item_cache, id, source);

    if (grl_tracker_source_can_notify (source)) {
      media = grl_tracker_build_grilo_media (type);
      if (media) {
        gchar *str_id = g_strdup_printf ("%i", id);
        gint change_type =
          GPOINTER_TO_INT (g_hash_table_lookup (evt->orphan_items,
                                                GSIZE_TO_POINTER (id)));

        grl_media_set_id (media, str_id);
        g_free (str_id);

        GRL_DEBUG ("\tNotify id=%u source=%s p=%p", id,
                   grl_source_get_name (GRL_SOURCE (source)),
                   source);
        grl_source_notify_change (GRL_SOURCE (source),
                                  media, change_type, FALSE);

        g_object_unref (media);
      }
    }
  }

  tracker_sparql_cursor_next_async (evt->cursor, NULL,
                                    (GAsyncReadyCallback) tracker_evt_update_orphan_item_cb,
                                    (gpointer) evt);
}
/* Callback invoked whenever a new source comes up */
static void
source_added_cb (GrlRegistry *registry,
                 GrlSource *source,
                 gpointer user_data)
{
  GrlSupportedOps supported_ops;
  MS2Server *server;
  const gchar *source_name;
  gchar *sanitized_source_id;
  gchar *source_id;

  /* Only sources that implement browse and resolve are of interest */
  supported_ops =
    grl_source_supported_operations (source);
  if (supported_ops & GRL_OP_BROWSE &&
      supported_ops & GRL_OP_RESOLVE) {

    source_id =
      (gchar *) grl_source_get_id (source);

    /* Check if there is already another provider with the same name */
    if (!dups) {
      source_name =
        grl_source_get_name (source);
      if (g_list_find_custom (providers_names,
                              source_name,
                              (GCompareFunc) g_strcmp0)) {
        g_debug ("Skipping %s [%s] source", source_id, source_name);
        return;
      }
    }

    /* Register a new service name */
    sanitized_source_id = g_strdup (source_id);

    g_debug ("Registering %s [%s] source", sanitized_source_id, source_name);

    sanitize (sanitized_source_id);

    server = ms2_server_new (sanitized_source_id, source);

    if (!server) {
      g_warning ("Cannot register %s", sanitized_source_id);
      g_free (sanitized_source_id);
    } else {
      ms2_server_set_get_properties_func (server, get_properties_cb);
      ms2_server_set_list_children_func (server, list_children_cb);
      /* Add search  */
      if (supported_ops & GRL_OP_SEARCH) {
        ms2_server_set_search_objects_func (server, search_objects_cb);
      }
      /* Save reference */
      if (!dups) {
        providers_names = g_list_prepend (providers_names,
                                          g_strdup(source_name));
      }
      g_hash_table_insert (servers, sanitized_source_id, server);
    }
  } else {
    g_debug ("%s source does not support either browse or resolve",
             grl_source_get_id (source));
  }
}