[epiphany/history-rewrite: 9/45] Add missing history service files from my previous commit.



commit b86859f91101d2f43408b62ebd05ea0a58104da3
Author: Martin Robinson <mrobinson igalia com>
Date:   Fri May 13 15:53:36 2011 -0700

    Add missing history service files from my previous commit.

 lib/history/ephy-history-service-private.h      |   44 ++
 lib/history/ephy-history-service-urls-table.c   |  189 +++++++++
 lib/history/ephy-history-service-visits-table.c |  142 +++++++
 lib/history/ephy-history-service.c              |  503 +++++++++++++++++++++++
 lib/history/ephy-history-service.h              |   62 +++
 lib/history/ephy-history-types.c                |  129 ++++++
 lib/history/ephy-history-types.h                |   81 ++++
 7 files changed, 1150 insertions(+), 0 deletions(-)
---
diff --git a/lib/history/ephy-history-service-private.h b/lib/history/ephy-history-service-private.h
new file mode 100644
index 0000000..f362126
--- /dev/null
+++ b/lib/history/ephy-history-service-private.h
@@ -0,0 +1,44 @@
+/*
+ *  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.
+ */
+
+#ifndef EPHY_HISTORY_SERVICE_PRIVATE_H
+#define EPHY_HISTORY_SERVICE_PRIVATE_H
+
+#include "../sqlite/ephy-sqlite-connection.h"
+
+struct _EphyHistoryServicePrivate {
+  char *history_filename;
+  EphySQLiteConnection *history_database;
+  GThread *history_thread;
+  GMutex *history_thread_mutex;
+  GMainLoop *history_thread_main_loop;
+  GMainContext *history_thread_main_context; 
+  gboolean active;
+};
+
+void                     ephy_history_service_schedule_commit         (EphyHistoryService *self);
+gboolean                 ephy_history_service_initialize_urls_table   (EphyHistoryService *self);
+EphyHistoryURL *         ephy_history_service_get_url_row             (EphyHistoryService *self, const char *url_string, EphyHistoryURL *url);
+void                     ephy_history_service_add_url_row             (EphyHistoryService *self, EphyHistoryURL *url);
+void                     ephy_history_service_update_url_row          (EphyHistoryService *self, EphyHistoryURL *url);
+
+gboolean                 ephy_history_service_initialize_visits_table (EphyHistoryService *self);
+void                     ephy_history_service_add_visit_row           (EphyHistoryService *self, EphyHistoryPageVisit *visit);
+GList *                  ephy_history_service_find_visit_rows         (EphyHistoryService *self, EphyHistoryQuery *query);
+
+#endif /* EPHY_HISTORY_SERVICE_PRIVATE_H */
diff --git a/lib/history/ephy-history-service-urls-table.c b/lib/history/ephy-history-service-urls-table.c
new file mode 100644
index 0000000..814dbe5
--- /dev/null
+++ b/lib/history/ephy-history-service-urls-table.c
@@ -0,0 +1,189 @@
+/*
+ *  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"
+
+gboolean
+ephy_history_service_initialize_urls_table (EphyHistoryService *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  GError *error = NULL;
+
+  if (ephy_sqlite_connection_table_exists (priv->history_database, "visits")) {
+    return TRUE;
+  }
+  ephy_sqlite_connection_execute (priv->history_database,
+    "CREATE TABLE urls ("
+    "id INTEGER PRIMARY KEY,"
+    "url LONGVARCAR,"
+    "title LONGVARCAR,"
+    "visit_count INTEGER DEFAULT 0 NOT NULL,"
+    "typed_count INTEGER DEFAULT 0 NOT NULL,"
+    "last_visit_time INTEGER,"
+    "favicon_id INTEGER DEFAULT 0 NOT NULL)", &error);
+
+  if (error) {
+    g_error("Could not create urls table: %s", error->message);
+    g_error_free (error);
+    return FALSE;
+  }
+  ephy_history_service_schedule_commit (self);
+  return TRUE;
+}
+
+EphyHistoryURL *
+ephy_history_service_get_url_row (EphyHistoryService *self, const char *url_string, EphyHistoryURL *url)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  EphySQLiteStatement *statement = NULL;  
+  GError *error = NULL;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_assert (priv->history_database != NULL);
+
+  if (url_string == NULL && url != NULL) {
+    url_string = url->url;
+  }
+  g_assert (url_string || url->id != -1);
+
+  if (url->id != -1) {
+    statement = ephy_sqlite_connection_create_statement (priv->history_database,
+      "SELECT id, url, title, visit_count, typed_count, last_visit_time FROM urls "
+      "WHERE id=?", &error);
+  } else {
+    statement = ephy_sqlite_connection_create_statement (priv->history_database,
+      "SELECT id, url, title, visit_count, typed_count, last_visit_time FROM urls "
+      "WHERE url=?", &error);
+  }
+
+  if (error) {
+    g_error ("Could not build urls table query statement: %s", error->message);
+    g_error_free (error);
+    return NULL;
+  }
+
+  if (url->id != -1) {
+    ephy_sqlite_statement_bind_int (statement, 0, url->id, &error);
+  } else {
+    ephy_sqlite_statement_bind_string (statement, 0, url_string, &error);
+  }
+  if (error) {
+    g_error ("Could not build urls table query statement: %s", error->message);
+    g_error_free (error);
+    g_object_unref (statement);
+    return NULL;
+  }
+
+  if (ephy_sqlite_statement_step (statement, &error) == FALSE) {
+    g_object_unref (statement);
+    return NULL;
+  }
+
+  if (url == NULL) {
+    url = ephy_history_url_new (NULL, NULL, 0, 0, 0);
+  }
+  url->id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+  url->url = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 1)),
+  url->title = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 2)),
+  url->visit_count = ephy_sqlite_statement_get_column_as_int (statement, 3),
+  url->typed_count = ephy_sqlite_statement_get_column_as_int (statement, 4),
+  url->last_visit_time = ephy_sqlite_statement_get_column_as_int (statement, 5);
+
+  g_object_unref (statement);
+  return url;
+}
+
+void
+ephy_history_service_add_url_row (EphyHistoryService *self, EphyHistoryURL *url)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  EphySQLiteStatement *statement = NULL;  
+  GError *error = NULL;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_assert (priv->history_database != NULL);
+
+  statement = ephy_sqlite_connection_create_statement (priv->history_database,
+    "INSERT INTO urls (url, title, visit_count, typed_count, last_visit_time) "
+    " VALUES (?, ?, ?, ?, ?)", &error);
+  if (error) {
+    g_error ("Could not build urls table addition statement: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  if (ephy_sqlite_statement_bind_string (statement, 0, url->url, &error) == FALSE ||
+      ephy_sqlite_statement_bind_string (statement, 1, url->title, &error) == FALSE || 
+      ephy_sqlite_statement_bind_int (statement, 2, url->visit_count, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 3, url->typed_count, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 4, url->last_visit_time, &error) == FALSE) {
+    g_error ("Could not insert URL into urls table: %s", error->message);
+    g_object_unref (error);
+    return;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error) {
+    g_error ("Could not insert URL into urls table: %s", error->message);
+    g_object_unref (error);
+  } else {
+    url->id = ephy_sqlite_connection_get_last_insert_id (priv->history_database);
+  }
+
+  g_object_unref (statement);
+}
+
+void
+ephy_history_service_update_url_row (EphyHistoryService *self, EphyHistoryURL *url)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  EphySQLiteStatement *statement;
+  GError *error = NULL;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_assert (priv->history_database != NULL);
+
+  statement = ephy_sqlite_connection_create_statement (priv->history_database,
+    "UPDATE urls SET title=?, visit_count=?, typed_count=?, last_visit_time=? "
+    "WHERE id=?", &error);
+  if (error) {
+    g_error ("Could not build urls table modification statement: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  if (ephy_sqlite_statement_bind_string (statement, 0, url->title, &error) == FALSE || 
+      ephy_sqlite_statement_bind_int (statement, 1, url->visit_count, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 2, url->typed_count, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 3, url->last_visit_time, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 4, url->id, &error) == FALSE) {
+    g_error ("Could not modify URL in urls table: %s", error->message);
+    g_object_unref (error);
+    return;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error) {
+    g_error ("Could not modify URL in urls table: %s", error->message);
+    g_object_unref (error);
+  }
+  g_object_unref (statement);
+}
diff --git a/lib/history/ephy-history-service-visits-table.c b/lib/history/ephy-history-service-visits-table.c
new file mode 100644
index 0000000..bd3e57b
--- /dev/null
+++ b/lib/history/ephy-history-service-visits-table.c
@@ -0,0 +1,142 @@
+/*
+ *  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"
+
+gboolean
+ephy_history_service_initialize_visits_table (EphyHistoryService *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  GError *error = NULL;
+
+  if (ephy_sqlite_connection_table_exists (priv->history_database, "visits")) {
+    return TRUE;
+  }
+  ephy_sqlite_connection_execute (priv->history_database,
+    "CREATE TABLE visits ("
+    "id INTEGER PRIMARY KEY,"
+    "url INTEGER NOT NULL,"
+    "visit_time INTEGER NOT NULL,"
+    "visit_type INTEGER NOT NULL,"
+    "referring_visit INTEGER)", &error);
+
+  if (error) {
+    g_error("Could not create visits table: %s", error->message);
+    g_error_free (error);
+    return FALSE;
+  }
+  ephy_history_service_schedule_commit (self);
+  return TRUE;
+}
+
+void
+ephy_history_service_add_visit_row (EphyHistoryService *self, EphyHistoryPageVisit *visit)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  EphySQLiteStatement *statement;
+  GError *error = NULL;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_assert (priv->history_database != NULL);
+
+  statement = ephy_sqlite_connection_create_statement (
+    priv->history_database,
+    "INSERT INTO visits (url, visit_time, visit_type) "
+    " VALUES (?, ?, ?) ", &error);
+  if (error) {
+    g_error ("Could not build visits table addition statement: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  if (ephy_sqlite_statement_bind_int (statement, 0, visit->url->id, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 1, visit->visit_time, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 2, visit->visit_type, &error) == FALSE ) {
+    g_error ("Could not build visits table addition statement: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error) {
+    g_error ("Could not insert URL into visits table: %s", error->message);
+    g_object_unref (error);
+  } else {
+    visit->id = ephy_sqlite_connection_get_last_insert_id (priv->history_database);
+  }
+
+  ephy_history_service_schedule_commit (self);
+  g_object_unref (statement);
+}
+
+static EphyHistoryPageVisit *
+create_page_visit_from_statement (EphySQLiteStatement *statement)
+{
+  EphyHistoryPageVisit *visit = 
+    ephy_history_page_visit_new (NULL,
+                                 ephy_sqlite_statement_get_column_as_int (statement, 1),
+                                 ephy_sqlite_statement_get_column_as_int (statement, 2));
+  visit->url->id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+  return visit;
+}
+
+GList *
+ephy_history_service_find_visit_rows (EphyHistoryService *self, EphyHistoryQuery *query)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  EphySQLiteStatement *statement = NULL;
+  GList *visits = NULL;
+  GError *error = NULL;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_assert (priv->history_database != NULL);
+
+  statement = ephy_sqlite_connection_create_statement (priv->history_database,
+    "SELECT url, visit_time, visit_type FROM visits WHERE visit_time >= ? and visit_time <= ?", &error);
+  if (error) {
+    g_error ("Could not build visits table query statement: %s", error->message);
+    g_error_free (error);
+    return NULL;
+  }
+
+  if (ephy_sqlite_statement_bind_int (statement, 0, (int) query->from, &error) == FALSE ||
+      ephy_sqlite_statement_bind_int (statement, 1, (int) query->to, &error) == FALSE) {
+    g_error ("Could not build urls table query statement: %s", error->message);
+    g_error_free (error);
+    g_object_unref (statement);
+    return NULL;
+  }
+
+  while (ephy_sqlite_statement_step (statement, &error)) {
+    visits = g_list_append (visits, create_page_visit_from_statement (statement));
+  }
+
+  if (error) {
+    g_error ("Could not execute urls table query statement: %s", error->message);
+    g_error_free (error);
+    g_object_unref (statement);
+    ephy_history_page_visit_list_free (visits);
+    return NULL;
+  }
+
+  g_object_unref (statement);
+  return visits;
+}
diff --git a/lib/history/ephy-history-service.c b/lib/history/ephy-history-service.c
new file mode 100644
index 0000000..c0f39a7
--- /dev/null
+++ b/lib/history/ephy-history-service.c
@@ -0,0 +1,503 @@
+/* *  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 "../sqlite/ephy-sqlite-connection.h"
+
+typedef gboolean (*EphyHistoryJobMethod)                                  (EphyHistoryService *self, gpointer data, gpointer *result);
+
+static void ephy_history_service_class_init                               (EphyHistoryServiceClass *klass);
+static void ephy_history_service_init                                     (EphyHistoryService *self);
+static void ephy_history_service_finalize                                 (GObject *self);
+static void ephy_history_service_dispose                                  (GObject *self);
+static gpointer run_history_service_thread                                (EphyHistoryService *self);
+static gboolean ephy_history_service_queue_stop_history_service_thread    (EphyHistoryService *self);
+static void ephy_history_service_schedule_idle                            (EphyHistoryService *self, int priority, GSourceFunc callback, gpointer data);
+static void ephy_history_service_schedule_source                          (EphyHistoryService *self, int priority, GSource *source, GSourceFunc callback, gpointer data);
+
+enum
+{
+  PROP_0,
+  PROP_HISTORY_FILENAME,
+};
+
+enum {
+    LAST_SIGNAL
+};
+
+#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_class_init (EphyHistoryServiceClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = ephy_history_service_finalize;
+  gobject_class->dispose = ephy_history_service_dispose;
+  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)
+{
+  GError *error = NULL;
+  self->priv = EPHY_HISTORY_SERVICE_GET_PRIVATE (self);
+
+  self->priv->history_thread_mutex = g_mutex_new ();
+  self->priv->history_thread_main_context = g_main_context_new ();
+  self->priv->history_database = NULL;
+  self->priv->active = TRUE;
+
+  self->priv->history_thread = g_thread_create ((GThreadFunc) run_history_service_thread, self, TRUE, &error);
+  if (NULL != error) {
+    g_error("Could not start history thread: %s", error->message);
+    g_error_free (error);
+    self->priv->history_thread = NULL;
+  }
+}
+
+static void
+ephy_history_service_dispose (GObject *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+
+  if (NULL == priv->history_thread_mutex) {
+    return;
+  }
+
+  {
+    g_mutex_lock (priv->history_thread_mutex);
+    priv->active = FALSE;
+
+    /* This will be a no-op if the main thread is no longer active or in the process of shutting down. */
+    if (priv->history_thread_main_loop) {
+      ephy_history_service_schedule_source (EPHY_HISTORY_SERVICE (self),
+                                            G_PRIORITY_HIGH, g_timeout_source_new (0),
+                                            (GSourceFunc) ephy_history_service_queue_stop_history_service_thread, self);
+    }
+    g_mutex_unlock (priv->history_thread_mutex);
+  }
+
+  G_OBJECT_CLASS (ephy_history_service_parent_class)->dispose (self);
+}
+
+static void
+ephy_history_service_finalize (GObject *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  if (NULL != priv->history_thread) {
+    g_thread_join (priv->history_thread);
+  }
+
+  if (NULL != priv->history_thread_mutex) {
+    g_mutex_free (priv->history_thread_mutex);
+  }
+
+  if (NULL != priv->history_thread_main_context) {
+    g_main_context_unref (priv->history_thread_main_context);
+  }
+
+  G_OBJECT_CLASS (ephy_history_service_parent_class)->finalize (self);
+}
+
+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 void
+ephy_history_service_schedule_source (EphyHistoryService *self, int priority, GSource *source, GSourceFunc callback, gpointer data)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+
+  g_source_set_priority (source, priority);
+  g_source_set_callback (source, callback, data, NULL);
+  g_source_attach (source, priv->history_thread_main_context);
+  g_source_unref (source);
+}
+
+static void
+ephy_history_service_schedule_idle (EphyHistoryService *self, int priority, GSourceFunc callback, gpointer data)
+{
+  ephy_history_service_schedule_source (self, priority, g_idle_source_new (), callback, data);
+}
+
+static gboolean
+ephy_history_service_commit (EphyHistoryService *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  GError *error = NULL;
+  g_assert (priv->history_thread == g_thread_self ());
+
+  if (NULL == priv->history_database) {
+    return FALSE;
+  }
+
+  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);
+  }
+  return FALSE;
+}
+
+void
+ephy_history_service_schedule_commit (EphyHistoryService *self)
+{
+  ephy_history_service_schedule_idle (self, G_PRIORITY_DEFAULT, (GSourceFunc) ephy_history_service_commit, self);
+}
+
+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_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_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 gpointer
+run_history_service_thread (EphyHistoryService *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+
+  g_assert (priv->history_thread == g_thread_self ());
+  g_main_context_push_thread_default (priv->history_thread_main_context);
+
+  {
+    g_mutex_lock (priv->history_thread_mutex);
+    if (priv->active == FALSE) {
+      g_mutex_unlock (priv->history_thread_mutex);
+      return NULL;
+    }
+    priv->history_thread_main_loop = g_main_loop_new (priv->history_thread_main_context, FALSE);
+    g_mutex_unlock (priv->history_thread_mutex);
+  }
+
+  if (ephy_history_service_open_database_connections (self) == FALSE) {
+    g_object_unref (priv->history_thread_main_loop);
+    return NULL;
+  }
+
+  g_main_loop_run (priv->history_thread_main_loop);
+
+  /* We want to avoid the situation where you check if the history thread
+     exists on another thread and then have it dereffed out of existence on
+     this thread, so we guard this operation with the mutex. */
+  { 
+    g_mutex_lock (priv->history_thread_mutex);
+    g_main_loop_unref (priv->history_thread_main_loop);
+    priv->history_thread_main_loop = NULL;
+    g_mutex_unlock (priv->history_thread_mutex);
+  }
+
+  ephy_history_service_close_database_connections (self);
+
+  return NULL;
+}
+
+static gboolean
+ephy_history_service_queue_stop_history_service_thread (EphyHistoryService *self)
+{
+  EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+  g_assert (priv->history_thread == g_thread_self ());
+
+  // TODO: Remove all main thread sources here.
+
+  ephy_history_service_commit (self);
+  g_main_loop_quit (priv->history_thread_main_loop);
+  return FALSE;
+}
+
+typedef struct _EphyHistoryThreadJobDetails
+{
+  EphyHistoryService *service;
+  EphyHistoryJobMethod method;
+  gpointer *method_argument;
+  gboolean success;
+  gpointer result;
+  gpointer user_data;
+  GDestroyNotify method_argument_cleanup;
+  EphyHistoryJobCallback callback;
+} EphyHistoryThreadJobDetails;
+
+static EphyHistoryThreadJobDetails *
+ephy_history_thread_job_details_new (EphyHistoryService *service,
+                                     EphyHistoryJobMethod method,
+                                     gpointer method_argument,
+                                     GDestroyNotify method_argument_cleanup,
+                                     EphyHistoryJobCallback callback,
+                                     gpointer user_data)
+{
+  EphyHistoryThreadJobDetails *details = g_slice_alloc0 (sizeof (EphyHistoryThreadJobDetails));
+  details->service = service; 
+  details->method = method;
+  details->method_argument = method_argument;
+  details->callback = callback;
+  details->user_data = user_data;
+  return details;
+}
+
+static void
+ephy_history_thread_job_details_free (EphyHistoryThreadJobDetails *details)
+{
+  if (details->method_argument_cleanup) {
+    details->method_argument_cleanup (details->method_argument);
+  }
+  g_slice_free1 (sizeof (EphyHistoryThreadJobDetails), details);
+}
+
+static gboolean
+ephy_history_service_execute_job_callback (gpointer data)
+{
+  EphyHistoryThreadJobDetails *details = (EphyHistoryThreadJobDetails*) data;
+  g_assert (details->callback);
+  details->callback (details->service, details->success, details->result, details->user_data);
+  ephy_history_thread_job_details_free (details);
+
+  return FALSE;
+}
+
+static gboolean
+ephy_history_service_execute_job_on_history_thread (gpointer data)
+{
+  EphyHistoryThreadJobDetails *details = (EphyHistoryThreadJobDetails*) data;
+
+  /* TODO: Here we must check if the history thread is shutting down
+     in which case we need to free the details and abort */
+
+  details->result = NULL;
+  details->success = details->method (details->service, details->method_argument, &details->result);
+
+  if (details->callback) {
+    g_idle_add (ephy_history_service_execute_job_callback, details);
+  } else {
+    ephy_history_thread_job_details_free (details);
+  }
+
+  return FALSE;
+}
+
+static gboolean
+ephy_history_service_execute_add_visit_helper (EphyHistoryService *self, EphyHistoryPageVisit *visit)
+{
+  /* 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);
+  ephy_history_service_schedule_commit (self);
+  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)
+{
+  EphyHistoryThreadJobDetails *details = 
+      ephy_history_thread_job_details_new (self, 
+                                           (EphyHistoryJobMethod) ephy_history_service_execute_add_visit, 
+                                           ephy_history_page_visit_copy (visit),
+                                           (GDestroyNotify) ephy_history_page_visit_free,
+                                           callback, user_data);
+  ephy_history_service_schedule_idle (self, G_PRIORITY_DEFAULT, 
+                                      ephy_history_service_execute_job_on_history_thread, 
+                                      details);
+}
+
+void
+ephy_history_service_add_visits (EphyHistoryService *self, GList *visits, EphyHistoryJobCallback callback, gpointer user_data)
+{
+  EphyHistoryThreadJobDetails *details = 
+      ephy_history_thread_job_details_new (self, 
+                                           (EphyHistoryJobMethod) ephy_history_service_execute_add_visits, 
+                                           ephy_history_page_visit_list_copy (visits),
+                                           (GDestroyNotify) ephy_history_page_visit_list_free,
+                                           callback, user_data);
+  ephy_history_service_schedule_idle (self, G_PRIORITY_DEFAULT, 
+                                      ephy_history_service_execute_job_on_history_thread, 
+                                      details);
+
+}
+
+void
+ephy_history_service_find_visits_in_time (EphyHistoryService *self, gint64 from, gint64 to, EphyHistoryJobCallback callback, gpointer user_data)
+{
+  EphyHistoryThreadJobDetails *details;
+
+  EphyHistoryQuery *query = ephy_history_query_new ();
+  query->from = from;
+  query->to = to;
+
+  details = ephy_history_thread_job_details_new (self, 
+                                                 (EphyHistoryJobMethod) ephy_history_service_execute_find_visits,
+                                                 query, (GDestroyNotify) ephy_history_query_free, callback, user_data);
+  ephy_history_service_schedule_idle (self, G_PRIORITY_DEFAULT, 
+                                      ephy_history_service_execute_job_on_history_thread, 
+                                      details);
+
+}
diff --git a/lib/history/ephy-history-service.h b/lib/history/ephy-history-service.h
new file mode 100644
index 0000000..8911745
--- /dev/null
+++ b/lib/history/ephy-history-service.h
@@ -0,0 +1,62 @@
+/*
+ *  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.
+ */
+
+#ifndef EPHY_HISTORY_SERVICE_H
+#define EPHY_HISTORY_SERVICE_H
+
+#include <glib-object.h>
+#include "ephy-history-types.h"
+
+G_BEGIN_DECLS
+
+/* convenience macros */
+#define EPHY_TYPE_HISTORY_SERVICE             (ephy_history_service_get_type())
+#define EPHY_HISTORY_SERVICE(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj),EPHY_TYPE_HISTORY_SERVICE,EphyHistoryService))
+#define EPHY_HISTORY_SERVICE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass),EPHY_TYPE_HISTORY_SERVICE,EphyHistoryServiceClass))
+#define EPHY_IS_HISTORY_SERVICE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj),EPHY_TYPE_HISTORY_SERVICE))
+#define EPHY_IS_HISTORY_SERVICE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass),EPHY_TYPE_HISTORY_SERVICE))
+#define EPHY_HISTORY_SERVICE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj),EPHY_TYPE_HISTORY_SERVICE,EphyHistoryServiceClass))
+
+typedef struct _EphyHistoryService                EphyHistoryService;
+typedef struct _EphyHistoryServiceClass           EphyHistoryServiceClass;
+typedef struct _EphyHistoryServicePrivate         EphyHistoryServicePrivate;
+
+typedef void   (*EphyHistoryJobCallback)          (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data);
+
+struct _EphyHistoryService {
+     GObject parent;
+
+    /* private */
+    EphyHistoryServicePrivate *priv;
+};
+
+struct _EphyHistoryServiceClass {
+    GObjectClass parent_class;
+};
+
+GType                    ephy_history_service_get_type                (void);
+EphyHistoryService *     ephy_history_service_new                     (const char *history_filename);
+
+void                     ephy_history_service_add_visit               (EphyHistoryService *self, EphyHistoryPageVisit *visit, EphyHistoryJobCallback callback, gpointer user_data);
+void                     ephy_history_service_add_visits              (EphyHistoryService *self, GList *visits, EphyHistoryJobCallback callback, gpointer user_data);
+void                     ephy_history_service_find_visits_in_time     (EphyHistoryService *self, gint64 from, gint64 to, EphyHistoryJobCallback callback, gpointer user_data);
+
+G_END_DECLS
+
+#endif /* EPHY_HISTORY_SERVICE_H */
+
diff --git a/lib/history/ephy-history-types.c b/lib/history/ephy-history-types.c
new file mode 100644
index 0000000..732c695
--- /dev/null
+++ b/lib/history/ephy-history-types.c
@@ -0,0 +1,129 @@
+/*
+ *  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 <glib.h>
+
+#include "ephy-history-types.h"
+
+EphyHistoryPageVisit *
+ephy_history_page_visit_new_with_url (EphyHistoryURL *url, gint64 visit_time, EphyHistoryPageVisitType visit_type)
+{
+  EphyHistoryPageVisit *visit = g_slice_alloc0 (sizeof (EphyHistoryPageVisit));
+  visit->id = -1;
+  visit->url = url;
+  visit->visit_time = visit_time;
+  visit->visit_type = visit_type;
+  return visit;
+}
+
+EphyHistoryPageVisit *
+ephy_history_page_visit_new (const char *url, gint64 visit_time, EphyHistoryPageVisitType visit_type)
+{
+  return ephy_history_page_visit_new_with_url (ephy_history_url_new (url, "", 0, 0, 0),
+                                               visit_time, visit_type);
+}
+
+void
+ephy_history_page_visit_free (EphyHistoryPageVisit *visit)
+{
+  if (visit == NULL)
+    return;
+
+  ephy_history_url_free (visit->url);
+  g_slice_free1 (sizeof (EphyHistoryPageVisit), visit);
+}
+
+EphyHistoryPageVisit *
+ephy_history_page_visit_copy (EphyHistoryPageVisit *visit)
+{
+  EphyHistoryPageVisit *copy = ephy_history_page_visit_new_with_url (0, visit->visit_time, visit->visit_type);
+  copy->id = visit->id;
+  copy->url = ephy_history_url_copy (visit->url);
+  return copy;
+}
+
+GList *
+ephy_history_page_visit_list_copy (GList *original)
+{
+  GList *new = g_list_copy (original);
+  GList *current = new;
+  while (current) {
+    current->data = ephy_history_page_visit_copy ((EphyHistoryPageVisit *) current->data);
+    current = current->next;
+  }
+  return new;
+}
+
+void
+ephy_history_page_visit_list_free (GList *list)
+{
+  g_list_free_full (list, (GDestroyNotify) ephy_history_page_visit_free);
+}
+
+EphyHistoryURL *
+ephy_history_url_new (const char *url, const char *title, int visit_count, int typed_count, int last_visit_time)
+{
+  EphyHistoryURL *history_url = g_slice_alloc0 (sizeof (EphyHistoryURL));
+  history_url->id = -1;
+  history_url->url = g_strdup (url);
+  history_url->title = g_strdup (title);
+  history_url->visit_count = visit_count;
+  history_url->typed_count = typed_count;
+  history_url->last_visit_time = last_visit_time;
+  return history_url;
+}
+
+EphyHistoryURL *
+ephy_history_url_copy (EphyHistoryURL *url)
+{
+  EphyHistoryURL *copy;
+  if (url == NULL)
+    return NULL;
+
+  copy = ephy_history_url_new (url->url,
+                               url->title,
+                               url->visit_count,
+                               url->typed_count,
+                               url->last_visit_time);
+  copy->id = url->id;
+  return copy;
+}
+
+void
+ephy_history_url_free (EphyHistoryURL *url)
+{
+  if (url == NULL)
+    return;
+
+  g_free (url->url);
+  g_free (url->title);
+  g_slice_free1 (sizeof (EphyHistoryURL), url);
+}
+
+EphyHistoryQuery *
+ephy_history_query_new ()
+{
+  return (EphyHistoryQuery*) g_slice_alloc0 (sizeof (EphyHistoryQuery));
+}
+
+void
+ephy_history_query_free (EphyHistoryQuery *query)
+{
+  g_list_free_full (query->substring_list, g_free);
+  g_slice_free1 (sizeof (EphyHistoryQuery), query);
+}
diff --git a/lib/history/ephy-history-types.h b/lib/history/ephy-history-types.h
new file mode 100644
index 0000000..1a5c70f
--- /dev/null
+++ b/lib/history/ephy-history-types.h
@@ -0,0 +1,81 @@
+/*
+ *  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.
+ */
+
+#ifndef EPHY_HISTORY_TYPES_H
+#define EPHY_HISTORY_TYPES_H
+
+G_BEGIN_DECLS
+
+/*
+ * Page transition types heavily inspired by those used in Chromium. See:
+ * src/chrome/common/page_transition_types.h in the Chromium source code.
+ */
+typedef enum {
+  EPHY_PAGE_VISIT_LINK,
+  EPHY_PAGE_VISIT_TYPE,
+  EPHY_PAGE_VISIT_MANUAL_SUBFRAME,
+  EPHY_PAGE_VISIT_AUTO_SUBFRAME,
+  EPHY_PAGE_VISIT_STARTUP,
+  EPHY_PAGE_VISIT_FORM_SUBMISSION,
+  EPHY_PAGE_VISIT_FORM_RELOAD,
+} EphyHistoryPageVisitType;
+
+typedef struct _EphyHistoryURL
+{
+  int id;
+  char* url;
+  char* title;
+  int visit_count;
+  int typed_count;
+  int last_visit_time;
+} EphyHistoryURL;
+
+typedef struct _EphyHistoryPageVisit
+{
+  EphyHistoryURL* url;
+  int id;
+  gint64 visit_time;
+  EphyHistoryPageVisitType visit_type;
+} EphyHistoryPageVisit;
+
+typedef struct _EphyHistoryQuery
+{
+  gint64 from;
+  gint64 to;
+  guint limit;
+  GList* substring_list;
+} EphyHistoryQuery;
+
+EphyHistoryPageVisit *          ephy_history_page_visit_new (const char *url, gint64 visit_time, EphyHistoryPageVisitType visit_type);
+EphyHistoryPageVisit *          ephy_history_page_visit_new_with_url (EphyHistoryURL *url, gint64 visit_time, EphyHistoryPageVisitType visit_type);
+EphyHistoryPageVisit *          ephy_history_page_visit_copy (EphyHistoryPageVisit *visit);
+void                            ephy_history_page_visit_free (EphyHistoryPageVisit *visit);
+
+GList *                         ephy_history_page_visit_list_copy (GList* original);
+void                            ephy_history_page_visit_list_free (GList* list);
+
+EphyHistoryURL *                ephy_history_url_new (const char *url, const char* title, int visit_count, int typed_count, int last_visit_time);
+EphyHistoryURL *                ephy_history_url_copy (EphyHistoryURL *url);
+void                            ephy_history_url_free (EphyHistoryURL *url);
+
+EphyHistoryQuery *              ephy_history_query_new (void);
+void                            ephy_history_query_free (EphyHistoryQuery *query);
+
+G_END_DECLS
+
+#endif /* EPHY_HISTORY_TYPES_H */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]