aboutsummaryrefslogblamecommitdiffstats
path: root/lib/history/ephy-history-service.c
blob: 2f3df7f73ac65ba4999e35aaae5e549f8b47f6fb (plain) (tree)




























































































































































                                                                                                                                                                       
                                                                                                


                                               
                                                                                          





































































































































































                                                                                                 
                                                                                           
 





                                                             
 
                 


           
                                                                      
 

                                                                
 
                                                              




                                                         
                                                                         
 


                                                                                              






















































































                                                                                                                                           
                                       



                                                                                    
                                                    




                                                                                                                              
                                       



                                                                                         
                                                    















                                                                                                                                                
                                     
 
                                                                 
                                                                                                                                              
                                                    














                                                                                                                                        
                                     
 
                                                               
                                                                                                                                              
                                                    






























                                                                             
                                      


                                                                                  
                                                    




























                                                                                   
                                      


                                                                                  
                                                    





















                                                                
                                      


                                                             
                                                    



























































                                                                        
                                      



                                                                               
                                                    

























                                                                   
                                      


                                                                                                                    
                                                    






                                                           
                                      


                                                           
                                                    

































                                                                                           
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
/* *  Copyright © 2011 Igalia S.L.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "ephy-history-service.h"

#include "ephy-history-service-private.h"
#include "ephy-history-types.h"
#include "ephy-sqlite-connection.h"

typedef gboolean (*EphyHistoryServiceMethod)                              (EphyHistoryService *self, gpointer data, gpointer *result);

typedef enum {
  /* WRITE */
  SET_URL_TITLE,
  SET_URL_ZOOM_LEVEL,
  SET_URL_PROPERTY, /* We only need this SET_ ? */
  ADD_VISIT,
  ADD_VISITS,
  DELETE_URLS,
  /* QUIT */
  QUIT,
  /* READ */
  GET_URL,
  QUERY_URLS,
  QUERY_VISITS,
} EphyHistoryServiceMessageType;
  
typedef struct _EphyHistoryServiceMessage {
  EphyHistoryService *service;
  EphyHistoryServiceMessageType type;
  gpointer *method_argument;
  gboolean success;
  gpointer result;
  gpointer user_data;
  GDestroyNotify method_argument_cleanup;
  EphyHistoryJobCallback callback;
} EphyHistoryServiceMessage;

static gpointer run_history_service_thread                                (EphyHistoryService *self);
static void ephy_history_service_process_message                          (EphyHistoryService *self, EphyHistoryServiceMessage *message);
static gboolean ephy_history_service_execute_quit                         (EphyHistoryService *self, gpointer data, gpointer *result);
static void ephy_history_service_quit                                     (EphyHistoryService *self, EphyHistoryJobCallback callback, gpointer user_data);

enum {
  PROP_0,
  PROP_HISTORY_FILENAME,
};

#define EPHY_HISTORY_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), EPHY_TYPE_HISTORY_SERVICE, EphyHistoryServicePrivate))

G_DEFINE_TYPE (EphyHistoryService, ephy_history_service, G_TYPE_OBJECT);

static void
ephy_history_service_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
  EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);

  switch (property_id) {
    case PROP_HISTORY_FILENAME:
      g_free (self->priv->history_filename);
      self->priv->history_filename = g_strdup (g_value_get_string (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
      break;
  }
}

static void
ephy_history_service_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
  EphyHistoryService *self = EPHY_HISTORY_SERVICE (object);
  switch (property_id) {
    case PROP_HISTORY_FILENAME:
      g_value_set_string (value, self->priv->history_filename);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static void
ephy_history_service_finalize (GObject *self)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;

  ephy_history_service_quit (EPHY_HISTORY_SERVICE (self), NULL, NULL);

  if (priv->history_thread)
    g_thread_join (priv->history_thread);

  g_free (priv->history_filename);

  G_OBJECT_CLASS (ephy_history_service_parent_class)->finalize (self);
}

static void
ephy_history_service_class_init (EphyHistoryServiceClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = ephy_history_service_finalize;
  gobject_class->get_property = ephy_history_service_get_property;
  gobject_class->set_property = ephy_history_service_set_property;

  g_object_class_install_property (gobject_class,
                                   PROP_HISTORY_FILENAME,
                                   g_param_spec_string ("history-filename",
                                                        "History filename",
                                                        "The filename of the SQLite file holding containing history",
                                                        NULL,
                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));

  g_type_class_add_private (gobject_class, sizeof (EphyHistoryServicePrivate));
}

static void
ephy_history_service_init (EphyHistoryService *self)
{
  self->priv = EPHY_HISTORY_SERVICE_GET_PRIVATE (self);

  self->priv->active = TRUE;
  self->priv->history_thread = g_thread_new ("EphyHistoryService", (GThreadFunc) run_history_service_thread, self);
  self->priv->queue = g_async_queue_new ();
}

EphyHistoryService *
ephy_history_service_new (const char *history_filename)
{
  return EPHY_HISTORY_SERVICE (g_object_new (EPHY_TYPE_HISTORY_SERVICE,
                                             "history-filename", history_filename,
                                              NULL));
}

static gint
sort_messages (EphyHistoryServiceMessage* a, EphyHistoryServiceMessage* b, gpointer user_data)
{
  return a->type > b->type ? 1 : a->type == b->type ? 0 : -1;
}

static void
ephy_history_service_send_message (EphyHistoryService *self, EphyHistoryServiceMessage *message)
{
  EphyHistoryServicePrivate *priv = self->priv;

  g_async_queue_push_sorted (priv->queue, message, (GCompareDataFunc)sort_messages, NULL);
}

static void
ephy_history_service_commit (EphyHistoryService *self)
{
  EphyHistoryServicePrivate *priv = self->priv;
  GError *error = NULL;
  g_assert (priv->history_thread == g_thread_self ());

  if (NULL == priv->history_database)
    return;

  ephy_sqlite_connection_commit_transaction (priv->history_database, &error);
  if (NULL != error) {
    g_error ("Could not commit idle history database transaction: %s", error->message);
    g_error_free (error);
  }
  ephy_sqlite_connection_begin_transaction (priv->history_database, &error);
  if (NULL != error) {
    g_error ("Could not start long-running history database transaction: %s", error->message);
    g_error_free (error);
  }

  self->priv->scheduled_to_commit = FALSE;
}

static void
ephy_history_service_enable_foreign_keys (EphyHistoryService *self)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
  GError *error = NULL;

  if (NULL == priv->history_database)
    return;

  ephy_sqlite_connection_execute (priv->history_database,
                                  "PRAGMA foreign_keys = ON", &error);

  if (error) {
    g_error ("Could not enable foreign keys pragma: %s", error->message);
    g_error_free (error);
  }
}

static gboolean
ephy_history_service_open_database_connections (EphyHistoryService *self)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
  GError *error = NULL;

  g_assert (priv->history_thread == g_thread_self ());

  priv->history_database = ephy_sqlite_connection_new ();
  ephy_sqlite_connection_open (priv->history_database, priv->history_filename, &error);
  if (error) {
    g_object_unref (priv->history_database);
    priv->history_database = NULL;
    g_error ("Could not open history database: %s", error->message);
    g_error_free (error);
    return FALSE;
  }

  ephy_history_service_enable_foreign_keys (self);

  ephy_sqlite_connection_begin_transaction (priv->history_database, &error);
  if (error) {
    g_error ("Could not begin long running transaction in history database: %s", error->message);
    g_error_free (error);
    return FALSE;
  }

  if ((ephy_history_service_initialize_hosts_table (self) == FALSE) ||
      (ephy_history_service_initialize_urls_table (self) == FALSE) ||
      (ephy_history_service_initialize_visits_table (self) == FALSE))
    return FALSE;

  return TRUE;
}

static void
ephy_history_service_close_database_connections (EphyHistoryService *self)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;

  g_assert (priv->history_thread == g_thread_self ());

  ephy_sqlite_connection_close (priv->history_database);
  g_object_unref (priv->history_database);
  priv->history_database = NULL;
}

static gboolean
ephy_history_service_is_scheduled_to_quit (EphyHistoryService *self)
{
  return self->priv->scheduled_to_quit;
}

static gboolean
ephy_history_service_is_scheduled_to_commit (EphyHistoryService *self)
{
  return self->priv->scheduled_to_commit;
}

void
ephy_history_service_schedule_commit (EphyHistoryService *self)
{
  self->priv->scheduled_to_commit = TRUE;
}

static gboolean
ephy_history_service_execute_quit (EphyHistoryService *self, gpointer data, gpointer *result)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
  g_assert (priv->history_thread == g_thread_self ());

  if (ephy_history_service_is_scheduled_to_commit (self))
    ephy_history_service_commit (self);

  g_async_queue_unref (priv->queue);

  self->priv->scheduled_to_quit = TRUE;

  return FALSE;
}

static gpointer
run_history_service_thread (EphyHistoryService *self)
{
  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
  EphyHistoryServiceMessage *message;

  g_assert (priv->history_thread == g_thread_self ());

  if (ephy_history_service_open_database_connections (self) == FALSE)
    return NULL;

  do {
    message = g_async_queue_try_pop (priv->queue);
    if (!message) {
      /* Schedule commit if needed. */
      if (ephy_history_service_is_scheduled_to_commit (self))
        ephy_history_service_commit (self);

      /* Block the thread until there's data in the queue. */
      message = g_async_queue_pop (priv->queue);
    }

    /* Process item. */
    ephy_history_service_process_message (self, message);

  } while (!ephy_history_service_is_scheduled_to_quit (self));

  ephy_history_service_close_database_connections (self);
  ephy_history_service_execute_quit (self, NULL, NULL);

  return NULL;
}

static EphyHistoryServiceMessage *
ephy_history_service_message_new (EphyHistoryService *service,
                                  EphyHistoryServiceMessageType type,
                                  gpointer method_argument,
                                  GDestroyNotify method_argument_cleanup,
                                  EphyHistoryJobCallback callback,
                                  gpointer user_data)
{
  EphyHistoryServiceMessage *message = g_slice_alloc0 (sizeof (EphyHistoryServiceMessage));

  message->service = service; 
  message->type = type;
  message->method_argument = method_argument;
  message->method_argument_cleanup = method_argument_cleanup;
  message->callback = callback;
  message->user_data = user_data;

  return message;
}

static void
ephy_history_service_message_free (EphyHistoryServiceMessage *message)
{
  if (message->method_argument_cleanup)
    message->method_argument_cleanup (message->method_argument);

  g_slice_free1 (sizeof (EphyHistoryServiceMessage), message);
}

static gboolean
ephy_history_service_execute_job_callback (gpointer data)
{
  EphyHistoryServiceMessage *message = (EphyHistoryServiceMessage*) data;

  g_assert (message->callback);
  message->callback (message->service, message->success, message->result, message->user_data);
  ephy_history_service_message_free (message);

  return FALSE;
}

static gboolean
ephy_history_service_execute_add_visit_helper (EphyHistoryService *self, EphyHistoryPageVisit *visit)
{
  if (visit->url->host == NULL)
    visit->url->host = ephy_history_service_get_host_row_from_url (self, visit->url->url);

  visit->url->host->visit_count++;
  ephy_history_service_update_host_row (self, visit->url->host);

  /* A NULL return here means that the URL does not yet exist in the database */
  if (NULL == ephy_history_service_get_url_row (self, visit->url->url, visit->url)) {
    visit->url->last_visit_time = visit->visit_time;
    ephy_history_service_add_url_row (self, visit->url);

    if (visit->url->id == -1) {
      g_error ("Adding visit failed after failed URL addition.");
      return FALSE;
    }

  } else {
    visit->url->visit_count++;

    if (visit->visit_time > visit->url->last_visit_time)
      visit->url->last_visit_time = visit->visit_time;

    ephy_history_service_update_url_row (self, visit->url);
  }

  ephy_history_service_add_visit_row (self, visit);
  return visit->id != -1;
}

static gboolean
ephy_history_service_execute_add_visit (EphyHistoryService *self, EphyHistoryPageVisit *visit, gpointer *result)
{
  gboolean success;
  g_assert (self->priv->history_thread == g_thread_self ());

  success = ephy_history_service_execute_add_visit_helper (self, visit);
  return success;
}

static gboolean
ephy_history_service_execute_add_visits (EphyHistoryService *self, GList *visits, gpointer *result)
{
  gboolean success = TRUE;
  g_assert (self->priv->history_thread == g_thread_self ());

  while (visits) {
    success = success && ephy_history_service_execute_add_visit_helper (self, (EphyHistoryPageVisit *) visits->data);
    visits = visits->next;
  }

  ephy_history_service_schedule_commit (self);

  return success;
}

static gboolean
ephy_history_service_execute_find_visits (EphyHistoryService *self, EphyHistoryQuery *query, gpointer *result)
{
  GList *visits = ephy_history_service_find_visit_rows (self, query);
  GList *current = visits;

  /* FIXME: We don't have a good way to tell the difference between failures and empty returns */
  while (current) {
    EphyHistoryPageVisit *visit = (EphyHistoryPageVisit *) current->data;
    if (NULL == ephy_history_service_get_url_row (self, NULL, visit->url)) {
      ephy_history_page_visit_list_free (visits);
      g_error ("Tried to process an orphaned page visit");
      return FALSE;
    }

    current = current->next;
  }

  *result = visits;
  return TRUE;
}

void
ephy_history_service_add_visit (EphyHistoryService *self, EphyHistoryPageVisit *visit, EphyHistoryJobCallback callback, gpointer user_data)
{
  EphyHistoryServiceMessage *message = 
    ephy_history_service_message_new (self, ADD_VISIT,
                                      ephy_history_page_visit_copy (visit),
                                      (GDestroyNotify) ephy_history_page_visit_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

void
ephy_history_service_add_visits (EphyHistoryService *self, GList *visits, EphyHistoryJobCallback callback, gpointer user_data)
{
  EphyHistoryServiceMessage *message = 
    ephy_history_service_message_new (self, ADD_VISITS,
                                      ephy_history_page_visit_list_copy (visits),
                                      (GDestroyNotify) ephy_history_page_visit_list_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

void
ephy_history_service_find_visits_in_time (EphyHistoryService *self, gint64 from, gint64 to, EphyHistoryJobCallback callback, gpointer user_data)
{
  EphyHistoryQuery *query = ephy_history_query_new ();
  query->from = from;
  query->to = to;

  ephy_history_service_query_visits (self, query, callback, user_data);
  ephy_history_query_free (query);
}

void
ephy_history_service_query_visits (EphyHistoryService *self, EphyHistoryQuery *query, EphyHistoryJobCallback callback, gpointer user_data)
{
  EphyHistoryServiceMessage *message;

  message = ephy_history_service_message_new (self, QUERY_VISITS,
                                              ephy_history_query_copy (query), (GDestroyNotify) ephy_history_query_free, callback, user_data);
  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_query_urls (EphyHistoryService *self, EphyHistoryQuery *query, gpointer *result)
{
  GList *urls = ephy_history_service_find_url_rows (self, query);

  *result = urls;

  return TRUE;
}

void
ephy_history_service_query_urls (EphyHistoryService *self, EphyHistoryQuery *query, EphyHistoryJobCallback callback, gpointer user_data)
{
  EphyHistoryServiceMessage *message;

  message = ephy_history_service_message_new (self, QUERY_URLS,
                                              ephy_history_query_copy (query), (GDestroyNotify) ephy_history_query_free, callback, user_data);
  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_set_url_title (EphyHistoryService *self,
                                            EphyHistoryURL *url,
                                            gpointer *result)
{
  char *title = g_strdup (url->title);

  if (NULL == ephy_history_service_get_url_row (self, NULL, url)) {
    /* The URL is not yet in the database, so we can't update it.. */
    g_free (title);
    return FALSE;
  } else {
    g_free (url->title);
    url->title = title;
    ephy_history_service_update_url_row (self, url);
    ephy_history_service_schedule_commit (self);
    return TRUE;
  }
}

void
ephy_history_service_set_url_title (EphyHistoryService *self,
                                    const char *orig_url,
                                    const char *title,
                                    EphyHistoryJobCallback callback,
                                    gpointer user_data)
{
  EphyHistoryURL *url = ephy_history_url_new (orig_url, title, 0, 0, 0, 1.0);

  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, SET_URL_TITLE,
                                      url, (GDestroyNotify) ephy_history_url_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_set_url_zoom_level (EphyHistoryService *self,
                                                 EphyHistoryURL *url,
                                                 gpointer *result)
{
  double zoom_level = url->zoom_level;

  if (NULL == ephy_history_service_get_url_row (self, NULL, url)) {
    /* The URL is not yet in the database, so we can't update it.. */
    return FALSE;
  } else {
    url->zoom_level = zoom_level;
    ephy_history_service_update_url_row (self, url);
    ephy_history_service_schedule_commit (self);
    return TRUE;
  }
}

void
ephy_history_service_set_url_zoom_level (EphyHistoryService *self,
                                         const char *orig_url,
                                         const double zoom_level,
                                         EphyHistoryJobCallback callback,
                                         gpointer user_data)
{
  EphyHistoryURL *url = ephy_history_url_new (orig_url, NULL, 0, 0, 0, zoom_level);

  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, SET_URL_ZOOM_LEVEL,
                                      url, (GDestroyNotify) ephy_history_url_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_get_url (EphyHistoryService *self,
                                      const gchar *orig_url,
                                      gpointer *result)
{
  EphyHistoryURL *url;

  url = ephy_history_service_get_url_row (self, orig_url, NULL);

  *result = url;

  return url != NULL;
}

void
ephy_history_service_get_url (EphyHistoryService *self,
                              const char *url,
                              EphyHistoryJobCallback callback,
                              gpointer user_data)
{
  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, GET_URL,
                                      g_strdup (url), g_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_set_url_property (EphyHistoryService *self,
                                               GVariant *variant,
                                               gpointer *result)
{
  GVariant *value, *mvalue;
  gchar *url_string;
  EphyHistoryURL *url;
  EphyHistoryURLProperty property;

  g_variant_get (variant, "(s(iv))", &url_string, &property, &value);

  url = ephy_history_url_new (url_string, NULL, 0, 0, 0, 1.0);
  g_free (url_string);

  if (NULL == ephy_history_service_get_url_row (self, NULL, url)) {
    g_variant_unref (value);
    ephy_history_url_free (url);

    return FALSE;
  }

  switch (property) {
  case EPHY_HISTORY_URL_TITLE:
    if (url->title)
      g_free (url->title);
    mvalue = g_variant_get_maybe (value);
    if (mvalue) {
      url->title = g_variant_dup_string (mvalue, NULL);
      g_variant_unref (mvalue);
    } else {
      url->title = NULL;
    }
    break;
  case EPHY_HISTORY_URL_ZOOM_LEVEL:
    url->zoom_level = g_variant_get_double (value);
    break;
  default:
    g_assert_not_reached();
  }
  g_variant_unref (value);

  ephy_history_service_update_url_row (self, url);
  ephy_history_service_schedule_commit (self);

  return TRUE;
}

void
ephy_history_service_set_url_property (EphyHistoryService *self,
                                       const char *url,
                                       EphyHistoryURLProperty property,
                                       GVariant *value,
                                       EphyHistoryJobCallback callback,
                                       gpointer user_data)
{
  GVariant *variant = g_variant_new ("(s(iv))", url, property, value);

  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, SET_URL_PROPERTY,
                                      variant, (GDestroyNotify)g_variant_unref,
                                      callback, user_data);

  ephy_history_service_send_message (self, message);
}

static gboolean
ephy_history_service_execute_delete_urls (EphyHistoryService *self,
                                          GList *urls,
                                          gpointer *result)
{
  GList *l;
  EphyHistoryURL *url;

  for (l = urls; l != NULL; l = l->next) {
    url = l->data;
    ephy_history_service_delete_url (self, url);
  }

  ephy_history_service_schedule_commit (self);

  return TRUE;
}

void
ephy_history_service_delete_urls (EphyHistoryService *self,
                                  GList *urls,
                                  EphyHistoryJobCallback callback,
                                  gpointer user_data)
{
  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, DELETE_URLS, 
                                      ephy_history_url_list_copy (urls), (GDestroyNotify)ephy_history_url_list_free,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

static void
ephy_history_service_quit (EphyHistoryService *self,
                           EphyHistoryJobCallback callback,
                           gpointer user_data)
{
  EphyHistoryServiceMessage *message =
    ephy_history_service_message_new (self, QUIT, 
                                      NULL, NULL,
                                      callback, user_data);
  ephy_history_service_send_message (self, message);
}

static EphyHistoryServiceMethod methods[] = {
  (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_title,
  (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_zoom_level,
  (EphyHistoryServiceMethod)ephy_history_service_execute_set_url_property,
  (EphyHistoryServiceMethod)ephy_history_service_execute_add_visit,
  (EphyHistoryServiceMethod)ephy_history_service_execute_add_visits,
  (EphyHistoryServiceMethod)ephy_history_service_execute_delete_urls,
  (EphyHistoryServiceMethod)ephy_history_service_execute_quit,
  (EphyHistoryServiceMethod)ephy_history_service_execute_get_url,
  (EphyHistoryServiceMethod)ephy_history_service_execute_query_urls,
  (EphyHistoryServiceMethod)ephy_history_service_execute_find_visits
};

static void
ephy_history_service_process_message (EphyHistoryService *self,
                                      EphyHistoryServiceMessage *message)
{
  EphyHistoryServiceMethod method;

  g_assert (self->priv->history_thread == g_thread_self ());

  method = methods[message->type];
  message->result = NULL;
  message->success = method (message->service, message->method_argument, &message->result);

  if (message->callback)
    g_idle_add ((GSourceFunc)ephy_history_service_execute_job_callback, message);
  else
    ephy_history_service_message_free (message);

  return;
}