<commit_before>/*
 * Copyright (C) 2011-2012 Igalia S.L.
 * Copyright (C) 2011 Intel Corporation.
 *
 * Contact: <NAME> <<EMAIL>>
 *
 * Authors: <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <tracker-sparql.h>

#include "grl-tracker.h"
#include "grl-tracker-source-notif.h"
#include "grl-tracker-source-priv.h"
#include "grl-tracker-utils.h"

/* --------- Logging  -------- */

#define GRL_LOG_DOMAIN_DEFAULT tracker_notif_log_domain
GRL_LOG_DOMAIN_STATIC(tracker_notif_log_domain);

/* ------- Definitions ------- */

#define TRACKER_MEDIA_ITEM                                              \
  "SELECT rdf:type(?urn) tracker:id(?urn) nie:dataSource(?urn) "        \
  "WHERE { ?urn a nfo:FileDataObject . "                                \
  "FILTER (tracker:id(?urn) IN (%s)) }"

/**/

typedef struct {
  /* tables of items for which we know the source */
  GHashTable *inserted_items;
  GHashTable *deleted_items;
  GHashTable *updated_items;

  /* table of items for which we don't know the source */
  GHashTable *orphan_items;

  /* List of new/old sources */
  GList *new_sources;
  GList *old_sources;

  /* Convenient stuff (for tracker/own callbacks...) */
  TrackerSparqlCursor  *cursor;
  gboolean             in_use;
  GrlSourceChangeType  change_type;
} tracker_evt_update_t;

/**/

static guint tracker_dbus_signal_id = 0;

/**/
static tracker_evt_update_t *
tracker_evt_update_new (void)
{
  tracker_evt_update_t *evt = g_slice_new0 (tracker_evt_update_t);

  evt->inserted_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
  evt->deleted_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
  evt->updated_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);

  evt->orphan_items = g_hash_table_new (g_direct_hash, g_direct_equal);

  return evt;
}

static void
tracker_evt_update_free (tracker_evt_update_t *evt)
{
  if (!evt)
    return;

  GRL_DEBUG ("free evt=%p", evt);

  g_hash_table_destroy (evt->inserted_items);
  g_hash_table_destroy (evt->deleted_items);
  g_hash_table_destroy (evt->updated_items);

  g_hash_table_destroy (evt->orphan_items);

  g_list_free (evt->new_sources);
  g_list_free (evt->old_sources);

  g_slice_free (tracker_evt_update_t, evt);
}

static void
tracker_evt_update_source_add (tracker_evt_update_t *evt,
                               const gchar *id,
                               const gchar *source_name)
{
  GrlTrackerSource *source;
  GrlTrackerSourcePriv *priv;

  source = g_hash_table_lookup (grl_tracker_source_sources_modified, id);
  if (!source) {
    source = g_object_new (GRL_TRACKER_SOURCE_TYPE,
			   "source-id", id,
			   "source-name", source_name,
			   "source-desc", GRL_TRACKER_SOURCE_DESC,
			   "tracker-connection", grl_tracker_connection,
                           "tracker-datasource", id,
			   NULL);
    g_hash_table_insert (grl_tracker_source_sources_modified,
                         (gpointer) grl_tracker_source_get_tracker_source (source),
                         g_object_ref (source));
  } else {
    g_object_ref (source);
  }

  priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
  priv->state = GRL_TRACKER_SOURCE_STATE_INSERTING;
  priv->notification_ref++;

  evt->new_sources = g_list_append (evt->new_sources, source);

  GRL_DEBUG ("Preadd source p=%p name=%s id=%s count=%u",
             source, source_name, id, priv->notification_ref);

  g_object_unref (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);
}

static void
tracker_evt_postupdate_sources (tracker_evt_update_t *evt)
{
  GList *source;

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

  source = evt->old_sources;
  while (source != NULL) {
    grl_tracker_del_source (GRL_TRACKER_SOURCE (source->data));
    source = source->next;
  }

  source = evt->new_sources;
  while (source != NULL) {
    grl_tracker_add_source (GRL_TRACKER_SOURCE (source->data));
    source = source->next;
  }

  tracker_evt_update_free (evt);
}

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);
}

static void
tracker_evt_update_orphans_cb (GObject              *object,
                               GAsyncResult         *result,
                               tracker_evt_update_t *evt)
{
  GError *error = NULL;

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

  evt->cursor = tracker_sparql_connection_query_finish (grl_tracker_connection,
                                                        result, &error);

  if (error != NULL) {
    GRL_WARNING ("Could not execute sparql query: %s", error->message);

    g_error_free (error);
    tracker_evt_postupdate_sources (evt);
    return;
  }

  tracker_sparql_cursor_next_async (evt->cursor, NULL,
                                    (GAsyncReadyCallback) tracker_evt_update_orphan_item_cb,
                                    (gpointer) evt);
}

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
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);<commit_msg>tracker: fix inefficient usage of GHashTable<commit_after>/*
 * Copyright (C) 2011-2012 Igalia S.L.
 * Copyright (C) 2011 Intel Corporation.
 *
 * Contact: <NAME> <<EMAIL>>
 *
 * Authors: <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <tracker-sparql.h>

#include "grl-tracker.h"
#include "grl-tracker-source-notif.h"
#include "grl-tracker-source-priv.h"
#include "grl-tracker-utils.h"

/* --------- Logging  -------- */

#define GRL_LOG_DOMAIN_DEFAULT tracker_notif_log_domain
GRL_LOG_DOMAIN_STATIC(tracker_notif_log_domain);

/* ------- Definitions ------- */

#define TRACKER_MEDIA_ITEM                                              \
  "SELECT rdf:type(?urn) tracker:id(?urn) nie:dataSource(?urn) "        \
  "WHERE { ?urn a nfo:FileDataObject . "                                \
  "FILTER (tracker:id(?urn) IN (%s)) }"

/**/

typedef struct {
  /* tables of items for which we know the source */
  GHashTable *inserted_items;
  GHashTable *deleted_items;
  GHashTable *updated_items;

  /* table of items for which we don't know the source */
  GHashTable *orphan_items;

  /* List of new/old sources */
  GList *new_sources;
  GList *old_sources;

  /* Convenient stuff (for tracker/own callbacks...) */
  TrackerSparqlCursor  *cursor;
  gboolean             in_use;
  GrlSourceChangeType  change_type;
} tracker_evt_update_t;

/**/

static guint tracker_dbus_signal_id = 0;

/**/
static tracker_evt_update_t *
tracker_evt_update_new (void)
{
  tracker_evt_update_t *evt = g_slice_new0 (tracker_evt_update_t);

  evt->inserted_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
  evt->deleted_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
  evt->updated_items = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);

  evt->orphan_items = g_hash_table_new (g_direct_hash, g_direct_equal);

  return evt;
}

static void
tracker_evt_update_free (tracker_evt_update_t *evt)
{
  if (!evt)
    return;

  GRL_DEBUG ("free evt=%p", evt);

  g_hash_table_destroy (evt->inserted_items);
  g_hash_table_destroy (evt->deleted_items);
  g_hash_table_destroy (evt->updated_items);

  g_hash_table_destroy (evt->orphan_items);

  g_list_free (evt->new_sources);
  g_list_free (evt->old_sources);

  g_slice_free (tracker_evt_update_t, evt);
}

static void
tracker_evt_update_source_add (tracker_evt_update_t *evt,
                               const gchar *id,
                               const gchar *source_name)
{
  GrlTrackerSource *source;
  GrlTrackerSourcePriv *priv;

  source = g_hash_table_lookup (grl_tracker_source_sources_modified, id);
  if (!source) {
    source = g_object_new (GRL_TRACKER_SOURCE_TYPE,
			   "source-id", id,
			   "source-name", source_name,
			   "source-desc", GRL_TRACKER_SOURCE_DESC,
			   "tracker-connection", grl_tracker_connection,
                           "tracker-datasource", id,
			   NULL);
    g_hash_table_insert (grl_tracker_source_sources_modified,
                         (gpointer) grl_tracker_source_get_tracker_source (source),
                         g_object_ref (source));
  } else {
    g_object_ref (source);
  }

  priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
  priv->state = GRL_TRACKER_SOURCE_STATE_INSERTING;
  priv->notification_ref++;

  evt->new_sources = g_list_append (evt->new_sources, source);

  GRL_DEBUG ("Preadd source p=%p name=%s id=%s count=%u",
             source, source_name, id, priv->notification_ref);

  g_object_unref (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);
}

static void
tracker_evt_postupdate_sources (tracker_evt_update_t *evt)
{
  GList *source;

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

  source = evt->old_sources;
  while (source != NULL) {
    grl_tracker_del_source (GRL_TRACKER_SOURCE (source->data));
    source = source->next;
  }

  source = evt->new_sources;
  while (source != NULL) {
    grl_tracker_add_source (GRL_TRACKER_SOURCE (source->data));
    source = source->next;
  }

  tracker_evt_update_free (evt);
}

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);
}

static void
tracker_evt_update_orphans_cb (GObject              *object,
                               GAsyncResult         *result,
                               tracker_evt_update_t *evt)
{
  GError *error = NULL;

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

  evt->cursor = tracker_sparql_connection_query_finish (grl_tracker_connection,
                                                        result, &error);

  if (error != NULL) {
    GRL_WARNING ("Could not execute sparql query: %s", error->message);

    g_error_free (error);
    tracker_evt_postupdate_sources (evt);
    return;
  }

  tracker_sparql_cursor_next_async (evt->cursor, NULL,
                                    (GAsyncReadyCallback) tracker_evt_update_orphan_item_cb,
                                    (gpointer) evt);
}

static void
tracker_evt_update_orphans (tracker_evt_update_t *evt)
{
  gboolean first = TRUE;
  GString *request_str;
  GHashTableIter iter;
  gpointer key, value;
  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 ("");

  g_hash_table_iter_init (&iter, evt->orphan_items);
  while (g_hash_table_iter_next (&iter, &key, &value)) {
    guint id = GPOINTER_TO_UINT (key);
    GrlSourceChangeType change_type = GPOINTER_TO_INT (value);

    if (change_type != 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;
      }
    }
  }

  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
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);
{'fraction_non_alphanumeric': 0.10383135059482156, 'fraction_numerical': 0.0027991602519244225, 'mean_word_length': 2.5220271102895873, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 54, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2634665', 'n_tokens_mistral': 7713, 'n_tokens_neox': 7585, 'n_words': 1597}