aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXan Lopez <xlopez@igalia.com>2011-11-25 20:39:50 +0800
committerXan Lopez <xan@igalia.com>2012-03-07 04:49:42 +0800
commit374d66dd260b989f22eba25dad4fabc49da2d586 (patch)
treed32121ea4f1769a3a725c75f1cf01229cb533f95
parent77ee0fdf4c5383f2134608e741b73f51ef30f430 (diff)
downloadgsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.gz
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.bz2
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.lz
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.xz
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.zst
gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.zip
Add EphyHistoryService and helper classes
EphyHistoryService provides a high-level API to store history information. It will processed by a worker thread using SQLite to provide a fast, responsive service to the main UI. Based on the code by Martin Robinson (mrobinson@igalia.com) and Claudio Saavedra (csaavedra@igalia.com).
-rw-r--r--configure.ac1
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/history/ephy-history-service-hosts-table.c316
-rw-r--r--lib/history/ephy-history-service-private.h55
-rw-r--r--lib/history/ephy-history-service-urls-table.c371
-rw-r--r--lib/history/ephy-history-service-visits-table.c207
-rw-r--r--lib/history/ephy-history-service.c742
-rw-r--r--lib/history/ephy-history-service.h70
-rw-r--r--lib/history/ephy-history-types.c218
-rw-r--r--lib/history/ephy-history-types.h106
-rw-r--r--src/Makefile.am1
-rw-r--r--tests/Makefile.am6
-rw-r--r--tests/ephy-history.c324
13 files changed, 2418 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index 2a2113e47..a26c4cfc4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -329,6 +329,7 @@ doc/Makefile
doc/reference/Makefile
lib/Makefile
lib/egg/Makefile
+lib/history/Makefile
lib/widgets/Makefile
embed/Makefile
src/Makefile
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 1d7a26825..82fd33713 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = widgets egg
+SUBDIRS = widgets egg history
noinst_LTLIBRARIES = libephymisc.la
diff --git a/lib/history/ephy-history-service-hosts-table.c b/lib/history/ephy-history-service-hosts-table.c
new file mode 100644
index 000000000..8dcd3d7f4
--- /dev/null
+++ b/lib/history/ephy-history-service-hosts-table.c
@@ -0,0 +1,316 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * 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-string.h"
+#include <glib/gi18n.h>
+
+gboolean
+ephy_history_service_initialize_hosts_table (EphyHistoryService *self)
+{
+ EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+ GError *error = NULL;
+
+ if (ephy_sqlite_connection_table_exists (priv->history_database, "hosts")) {
+ return TRUE;
+ }
+ ephy_sqlite_connection_execute (priv->history_database,
+ "CREATE TABLE hosts ("
+ "id INTEGER PRIMARY KEY,"
+ "url LONGVARCAR,"
+ "title LONGVARCAR,"
+ "visit_count INTEGER DEFAULT 0 NOT NULL,"
+ "favicon_id INTEGER DEFAULT 0 NOT NULL)", &error);
+
+ if (error) {
+ g_error("Could not create hosts table: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ ephy_history_service_schedule_commit (self);
+ return TRUE;
+}
+
+void
+ephy_history_service_add_host_row (EphyHistoryService *self, EphyHistoryHost *host)
+{
+ 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 hosts (url, title, visit_count) "
+ "VALUES (?, ?, ?)", &error);
+
+ if (error) {
+ g_error ("Could not build hosts table addition statement: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (ephy_sqlite_statement_bind_string (statement, 0, host->url, &error) == FALSE ||
+ ephy_sqlite_statement_bind_string (statement, 1, host->title, &error) == FALSE ||
+ ephy_sqlite_statement_bind_int (statement, 2, host->visit_count, &error) == FALSE) {
+ g_error ("Could not insert host into hosts table: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ ephy_sqlite_statement_step (statement, &error);
+ if (error) {
+ g_error ("Could not insert host into hosts table: %s", error->message);
+ g_error_free (error);
+ return;
+ } else {
+ host->id = ephy_sqlite_connection_get_last_insert_id (priv->history_database);
+ }
+
+ g_object_unref (statement);
+}
+
+void
+ephy_history_service_update_host_row (EphyHistoryService *self, EphyHistoryHost *host)
+{
+ 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 hosts SET url=?, title=?, visit_count=?"
+ "WHERE id=?", &error);
+ if (error) {
+ g_error ("Could not build hosts table modification statement: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ if (ephy_sqlite_statement_bind_string (statement, 0, host->url, &error) == FALSE ||
+ ephy_sqlite_statement_bind_string (statement, 1, host->title, &error) == FALSE ||
+ ephy_sqlite_statement_bind_int (statement, 2, host->visit_count, &error) == FALSE ||
+ ephy_sqlite_statement_bind_int (statement, 3, host->id, &error) == FALSE) {
+ g_error ("Could not modify host in hosts table: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ ephy_sqlite_statement_step (statement, &error);
+ if (error) {
+ g_error ("Could not modify URL in urls table: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (statement);
+}
+
+EphyHistoryHost*
+ephy_history_service_get_host_row (EphyHistoryService *self, const gchar *host_string, EphyHistoryHost *host)
+{
+ 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 (host_string == NULL && host != NULL)
+ host_string = host->url;
+
+ g_assert (host_string || host->id !=-1);
+
+ if (host != NULL && host->id != -1) {
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ "SELECT id, url, title, visit_count FROM hosts "
+ "WHERE id=?", &error);
+ } else {
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ "SELECT id, url, title, visit_count FROM hosts "
+ "WHERE url=?", &error);
+ }
+
+ if (error) {
+ g_error ("Could not build hosts query statement: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ if (host != NULL && host->id != -1)
+ ephy_sqlite_statement_bind_int (statement, 0, host->id, &error);
+ else
+ ephy_sqlite_statement_bind_string (statement, 0, host_string, &error);
+
+ if (error) {
+ g_error ("Could not build hosts 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 (host == NULL) {
+ host = ephy_history_host_new (NULL, NULL, 0);
+ } else {
+ if (host->url)
+ g_free (host->url);
+ if (host->title)
+ g_free (host->title);
+ }
+
+ host->id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+ host->url = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 1)),
+ host->title = g_strdup (ephy_sqlite_statement_get_column_as_string (statement, 2)),
+ host->visit_count = ephy_sqlite_statement_get_column_as_int (statement, 3),
+
+ g_object_unref (statement);
+ return host;
+}
+
+static EphyHistoryHost*
+create_host_from_statement (EphySQLiteStatement *statement)
+{
+ EphyHistoryHost *host =
+ ephy_history_host_new (ephy_sqlite_statement_get_column_as_string (statement, 1),
+ ephy_sqlite_statement_get_column_as_string (statement, 2),
+ ephy_sqlite_statement_get_column_as_int (statement, 3));
+ host->id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+
+ return host;
+}
+
+GList*
+ephy_history_service_get_all_hosts (EphyHistoryService *self)
+{
+ EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+ EphySQLiteStatement *statement = NULL;
+ GList *hosts = 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 id, url, title, visit_count FROM hosts", &error);
+
+ if (error) {
+ g_error ("Could not build hosts query statement: %s", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ while (ephy_sqlite_statement_step (statement, &error))
+ hosts = g_list_prepend (hosts, create_host_from_statement (statement));
+
+ hosts = g_list_reverse (hosts);
+
+ if (error) {
+ g_error ("Could not execute hosts table query statement: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (statement);
+
+ return hosts;
+}
+
+/* Inspired from ephy-history.c */
+static GList *
+get_hostname_and_locations (const gchar *url, gchar **hostname)
+{
+ GList *host_locations = NULL;
+ char *scheme = NULL;
+
+ if (url) {
+ scheme = g_uri_parse_scheme (url);
+ *hostname = ephy_string_get_host_name (url);
+ }
+ /* Build an host name */
+ if (scheme == NULL || *hostname == NULL) {
+ *hostname = g_strdup (_("Others"));
+ host_locations = g_list_append (host_locations,
+ g_strdup ("about:blank"));
+ } else if (strcmp (scheme, "file") == 0) {
+ *hostname = g_strdup (_("Local files"));
+ host_locations = g_list_append (host_locations,
+ g_strdup ("file:///"));
+ } else {
+ char *location;
+ char *tmp;
+
+ if (g_str_equal (scheme, "https")) {
+ /* If scheme is https, we still fake http. */
+ location = g_strconcat ("http://", *hostname, "/", NULL);
+ host_locations = g_list_append (host_locations, location);
+ }
+
+ /* We append the real address */
+ location = g_strconcat (scheme,
+ "://", *hostname, "/", NULL);
+ host_locations = g_list_append (host_locations, location);
+
+ /* and also a fake www-modified address if it's http or https. */
+ if (g_str_has_prefix (scheme, "http")) {
+ if (g_str_has_prefix (*hostname, "www."))
+ tmp = g_strdup (*hostname + 4);
+ else
+ tmp = g_strconcat ("www.", *hostname, NULL);
+ location = g_strconcat ("http://", tmp, "/", NULL);
+ g_free (tmp);
+ host_locations = g_list_append (host_locations, location);
+ }
+ }
+ return host_locations;
+}
+
+EphyHistoryHost*
+ephy_history_service_get_host_row_from_url (EphyHistoryService *self,
+ const gchar *url)
+{
+ GList *host_locations, *l;
+ char *hostname;
+ EphyHistoryHost *host;
+
+ host_locations = get_hostname_and_locations (url, &hostname);
+
+ for (l = host_locations; l != NULL; l = l->next) {
+ host = ephy_history_service_get_host_row (self, l->data, NULL);
+ if (host != NULL)
+ break;
+ }
+
+ if (host == NULL) {
+ host = ephy_history_host_new (host_locations->data, hostname, 0);
+ ephy_history_service_add_host_row (self, host);
+ }
+
+ g_free (hostname);
+ g_list_free_full (host_locations, (GDestroyNotify) g_free);
+
+ return host;
+}
diff --git a/lib/history/ephy-history-service-private.h b/lib/history/ephy-history-service-private.h
new file mode 100644
index 000000000..4cf88d4c0
--- /dev/null
+++ b/lib/history/ephy-history-service-private.h
@@ -0,0 +1,55 @@
+/* -*- 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.
+ */
+
+#ifndef EPHY_HISTORY_SERVICE_PRIVATE_H
+#define EPHY_HISTORY_SERVICE_PRIVATE_H
+
+#include "ephy-sqlite-connection.h"
+
+struct _EphyHistoryServicePrivate {
+ char *history_filename;
+ EphySQLiteConnection *history_database;
+ GThread *history_thread;
+ GAsyncQueue *queue;
+ GMutex history_thread_mutex;
+ gboolean active;
+ gboolean scheduled_to_quit;
+ gboolean scheduled_to_commit;
+};
+
+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);
+GList* ephy_history_service_find_url_rows (EphyHistoryService *self, EphyHistoryQuery *query);
+void ephy_history_service_delete_url (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);
+
+gboolean ephy_history_service_initialize_hosts_table (EphyHistoryService *self);
+void ephy_history_service_add_host_row (EphyHistoryService *self, EphyHistoryHost *host);
+void ephy_history_service_update_host_row (EphyHistoryService *self, EphyHistoryHost *host);
+EphyHistoryHost * ephy_history_service_get_host_row (EphyHistoryService *self, const gchar *url_string, EphyHistoryHost *host);
+GList * ephy_history_service_get_all_hosts (EphyHistoryService *self);
+EphyHistoryHost * ephy_history_service_get_host_row_from_url (EphyHistoryService *self, const gchar *url);
+
+#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 000000000..3c7834bb8
--- /dev/null
+++ b/lib/history/ephy-history-service-urls-table.c
@@ -0,0 +1,371 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * 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,"
+ "host INTEGER NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,"
+ "url LONGVARCAR,"
+ "title LONGVARCAR,"
+ "visit_count INTEGER DEFAULT 0 NOT NULL,"
+ "typed_count INTEGER DEFAULT 0 NOT NULL,"
+ "last_visit_time INTEGER,"
+ "zoom_level REAL DEFAULT 1.0)", &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 != NULL && url->id != -1) {
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ "SELECT id, url, title, visit_count, typed_count, last_visit_time, zoom_level 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, zoom_level 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 != NULL && 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, 1.0);
+ } else {
+ if (url->url)
+ g_free (url->url);
+ if (url->title)
+ g_free (url->title);
+ }
+
+ 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);
+ url->zoom_level = ephy_sqlite_statement_get_column_as_double (statement, 6);
+
+ 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, zoom_level, host) "
+ " 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 ||
+ ephy_sqlite_statement_bind_double (statement, 5, url->zoom_level, &error) == FALSE ||
+ ephy_sqlite_statement_bind_int (statement, 6, url->host->id, &error) == FALSE) {
+ g_error ("Could not insert URL into urls table: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ ephy_sqlite_statement_step (statement, &error);
+ if (error) {
+ g_error ("Could not insert URL into urls table: %s", error->message);
+ g_error_free (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=?, zoom_level=? "
+ "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_double (statement, 4, url->zoom_level, &error) == FALSE ||
+ ephy_sqlite_statement_bind_int (statement, 5, url->id, &error) == FALSE) {
+ g_error ("Could not modify URL in urls table: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ ephy_sqlite_statement_step (statement, &error);
+ if (error) {
+ g_error ("Could not modify URL in urls table: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (statement);
+}
+
+static EphyHistoryURL *
+create_url_from_statement (EphySQLiteStatement *statement)
+{
+ EphyHistoryURL *url = ephy_history_url_new (ephy_sqlite_statement_get_column_as_string (statement, 1),
+ ephy_sqlite_statement_get_column_as_string (statement, 2),
+ ephy_sqlite_statement_get_column_as_int (statement, 3),
+ ephy_sqlite_statement_get_column_as_int (statement, 4),
+ ephy_sqlite_statement_get_column_as_int (statement, 5),
+ ephy_sqlite_statement_get_column_as_int (statement, 6));
+
+ url->id = ephy_sqlite_statement_get_column_as_int (statement, 0);
+ url->host = ephy_history_host_new (NULL, NULL, 0);
+ url->host->id = ephy_sqlite_statement_get_column_as_int (statement, 7);
+
+ return url;
+}
+
+GList *
+ephy_history_service_find_url_rows (EphyHistoryService *self, EphyHistoryQuery *query)
+{
+ EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+ EphySQLiteStatement *statement = NULL;
+ GList *substring;
+ GString *statement_str;
+ GList *urls = NULL;
+ GError *error = NULL;
+ const char *base_statement = ""
+ "SELECT "
+ "DISTINCT urls.id, "
+ "urls.url, "
+ "urls.title, "
+ "urls.visit_count, "
+ "urls.typed_count, "
+ "urls.last_visit_time, "
+ "urls.zoom_level, "
+ "urls.host "
+ "FROM "
+ "urls JOIN visits ON visits.url = urls.id "
+ "WHERE ";
+
+ int i = 0;
+
+ g_assert (priv->history_thread == g_thread_self ());
+ g_assert (priv->history_database != NULL);
+
+ statement_str = g_string_new (base_statement);
+
+ if (query->from >= 0)
+ statement_str = g_string_append (statement_str, "visits.visit_time >= ? AND ");
+ if (query->to >= 0)
+ statement_str = g_string_append (statement_str, "visits.visit_time <= ? AND ");
+
+ for (substring = query->substring_list; substring != NULL; substring = substring->next)
+ statement_str = g_string_append (statement_str, "(urls.url LIKE ? OR urls.title LIKE ?) AND ");
+
+ statement_str = g_string_append (statement_str, "1");
+
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ statement_str->str, &error);
+ g_string_free (statement_str, TRUE);
+
+ 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 (query->from >= 0) {
+ if (ephy_sqlite_statement_bind_int (statement, i++, (int)query->from, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ return NULL;
+ }
+ }
+ if (query->to >= 0) {
+ if (ephy_sqlite_statement_bind_int (statement, i++, (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;
+ }
+ }
+ for (substring = query->substring_list; substring != NULL; substring = substring->next) {
+ char *string = g_strdup_printf ("%%%s%%", (char*)substring->data);
+ if (ephy_sqlite_statement_bind_string (statement, i++, string, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ g_free (string);
+ return NULL;
+ }
+ if (ephy_sqlite_statement_bind_string (statement, i++, string, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ g_free (string);
+ return NULL;
+ }
+ g_free (string);
+ }
+
+ while (ephy_sqlite_statement_step (statement, &error))
+ urls = g_list_prepend (urls, create_url_from_statement (statement));
+
+ urls = g_list_reverse (urls);
+
+ if (error) {
+ g_error ("Could not execute urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ g_list_free_full (urls, (GDestroyNotify)ephy_history_url_free);
+ return NULL;
+ }
+
+ g_object_unref (statement);
+ return urls;
+}
+
+void
+ephy_history_service_delete_url (EphyHistoryService *self, EphyHistoryURL *url)
+{
+ EphyHistoryServicePrivate *priv = EPHY_HISTORY_SERVICE (self)->priv;
+ EphySQLiteStatement *statement = NULL;
+ gchar *sql_statement;
+ GError *error = NULL;
+
+ g_assert (priv->history_thread == g_thread_self ());
+ g_assert (priv->history_database != NULL);
+
+ g_assert (url->id != -1 || url->url);
+
+ if (url->id != -1)
+ sql_statement = g_strdup ("DELETE FROM urls WHERE id=?");
+ else
+ sql_statement = g_strdup ("DELETE FROM urls WHERE url=?");
+
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ sql_statement, &error);
+ g_free (sql_statement);
+
+ if (error) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ return;
+ }
+
+ if (url->id != -1)
+ ephy_sqlite_statement_bind_int (statement, 0, url->id, &error);
+ else
+ ephy_sqlite_statement_bind_string (statement, 0, url->url, &error);
+
+ if (error) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ return;
+ }
+
+ ephy_sqlite_statement_step (statement, &error);
+ if (error) {
+ g_error ("Could not modify URL in urls table: %s", error->message);
+ g_error_free (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 000000000..822a33653
--- /dev/null
+++ b/lib/history/ephy-history-service-visits-table.c
@@ -0,0 +1,207 @@
+/*
+ * 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 REFERENCES urls(id) ON DELETE CASCADE,"
+ "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_error_free (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 *substring;
+ GString *statement_str;
+ GList *visits = NULL;
+ GError *error = NULL;
+ const char *base_statement = ""
+ "SELECT "
+ "visits.url, "
+ "visits.visit_time, "
+ "visits.visit_type ";
+ const char *from_join_statement = ""
+ "FROM "
+ "visits JOIN urls ON visits.url = urls.id ";
+ const char *from_visits_statement = ""
+ "FROM "
+ "visits ";
+
+ int i = 0;
+
+ g_assert (priv->history_thread == g_thread_self ());
+ g_assert (priv->history_database != NULL);
+
+ statement_str = g_string_new (base_statement);
+
+ if (query->substring_list)
+ statement_str = g_string_append (statement_str, from_join_statement);
+ else
+ statement_str = g_string_append (statement_str, from_visits_statement);
+
+ statement_str = g_string_append (statement_str, "WHERE ");
+
+ if (query->from >= 0)
+ statement_str = g_string_append (statement_str, "visits.visit_time >= ? AND ");
+ if (query->to >= 0)
+ statement_str = g_string_append (statement_str, "visits.visit_time <= ? AND ");
+
+ for (substring = query->substring_list; substring != NULL; substring = substring->next) {
+ statement_str = g_string_append (statement_str, "(urls.url LIKE ? OR urls.title LIKE ?) AND ");
+ }
+
+ statement_str = g_string_append (statement_str, "1");
+
+ statement = ephy_sqlite_connection_create_statement (priv->history_database,
+ statement_str->str, &error);
+ g_string_free (statement_str, TRUE);
+
+ if (error) {
+ g_error ("Could not build visits table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ }
+
+ if (query->from >= 0) {
+ if (ephy_sqlite_statement_bind_int (statement, i++, (int)query->from, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ return NULL;
+ }
+ }
+ if (query->to >= 0) {
+ if (ephy_sqlite_statement_bind_int (statement, i++, (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;
+ }
+ }
+ for (substring = query->substring_list; substring != NULL; substring = substring->next) {
+ char *string = g_strdup_printf ("%%%s%%", (char*)substring->data);
+ if (ephy_sqlite_statement_bind_string (statement, i++, string, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ g_free (string);
+ return NULL;
+ }
+ if (ephy_sqlite_statement_bind_string (statement, i++, string, &error) == FALSE) {
+ g_error ("Could not build urls table query statement: %s", error->message);
+ g_error_free (error);
+ g_object_unref (statement);
+ g_free (string);
+ return NULL;
+ }
+ g_free (string);
+ }
+
+ while (ephy_sqlite_statement_step (statement, &error))
+ visits = g_list_prepend (visits, create_page_visit_from_statement (statement));
+
+ visits = g_list_reverse (visits);
+
+ 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 000000000..ae62a2329
--- /dev/null
+++ b/lib/history/ephy-history-service.c
@@ -0,0 +1,742 @@
+/* -*- 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, gpointer data)
+{
+ EphyHistoryServicePrivate *priv = self->priv;
+
+ g_async_queue_push_sorted (priv->queue, data, (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 *details = g_slice_alloc0 (sizeof (EphyHistoryServiceMessage));
+
+ details->service = service;
+ details->type = type;
+ details->method_argument = method_argument;
+ details->method_argument_cleanup = method_argument_cleanup;
+ details->callback = callback;
+ details->user_data = user_data;
+
+ return details;
+}
+
+static void
+ephy_history_service_message_free (EphyHistoryServiceMessage *details)
+{
+ if (details->method_argument_cleanup)
+ details->method_argument_cleanup (details->method_argument);
+
+ g_slice_free1 (sizeof (EphyHistoryServiceMessage), details);
+}
+
+static gboolean
+ephy_history_service_execute_job_callback (gpointer data)
+{
+ EphyHistoryServiceMessage *details = (EphyHistoryServiceMessage*) data;
+
+ g_assert (details->callback);
+ details->callback (details->service, details->success, details->result, details->user_data);
+ ephy_history_service_message_free (details);
+
+ 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 *details =
+ 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, details);
+}
+
+void
+ephy_history_service_add_visits (EphyHistoryService *self, GList *visits, EphyHistoryJobCallback callback, gpointer user_data)
+{
+ EphyHistoryServiceMessage *details =
+ 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, details);
+}
+
+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 *details;
+
+ details = 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, details);
+}
+
+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 *details;
+
+ details = 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, details);
+}
+
+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 *details =
+ ephy_history_service_message_new (self, SET_URL_TITLE,
+ url, (GDestroyNotify) ephy_history_url_free,
+ callback, user_data);
+ ephy_history_service_send_message (self, details);
+}
+
+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 *details =
+ 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, details);
+}
+
+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 *details =
+ ephy_history_service_message_new (self, GET_URL,
+ g_strdup (url), g_free,
+ callback, user_data);
+ ephy_history_service_send_message (self, details);
+}
+
+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 *details =
+ ephy_history_service_message_new (self, SET_URL_PROPERTY,
+ variant, (GDestroyNotify)g_variant_unref,
+ callback, user_data);
+
+ ephy_history_service_send_message (self, details);
+}
+
+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 *details =
+ 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, details);
+}
+
+static void
+ephy_history_service_quit (EphyHistoryService *self,
+ EphyHistoryJobCallback callback,
+ gpointer user_data)
+{
+ EphyHistoryServiceMessage *details =
+ ephy_history_service_message_new (self, QUIT,
+ NULL, NULL,
+ callback, user_data);
+ ephy_history_service_send_message (self, details);
+}
+
+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;
+}
diff --git a/lib/history/ephy-history-service.h b/lib/history/ephy-history-service.h
new file mode 100644
index 000000000..601d13b07
--- /dev/null
+++ b/lib/history/ephy-history-service.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * 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);
+void ephy_history_service_query_visits (EphyHistoryService *self, EphyHistoryQuery *query, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_query_urls (EphyHistoryService *self, EphyHistoryQuery *query, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_set_url_title (EphyHistoryService *self, const char *url, const char *title, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_set_url_zoom_level (EphyHistoryService *self, const char *url, const double zoom_level, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_set_url_property (EphyHistoryService *self, const char *url, EphyHistoryURLProperty property, GVariant *value, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_get_url (EphyHistoryService *self, const char *url, EphyHistoryJobCallback callback, gpointer user_data);
+void ephy_history_service_delete_urls (EphyHistoryService *self, GList *urls, 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 000000000..1a8fc4380
--- /dev/null
+++ b/lib/history/ephy-history-types.c
@@ -0,0 +1,218 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * 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, 1.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);
+}
+
+EphyHistoryHost *
+ephy_history_host_new (const char *url, const char *title, int visit_count)
+{
+ EphyHistoryHost *host = g_slice_alloc0 (sizeof (EphyHistoryHost));
+
+ host->id = -1;
+ host->url = g_strdup (url);
+ host->title = g_strdup (title);
+ host->visit_count = visit_count;
+
+ return host;
+}
+
+EphyHistoryHost *
+ephy_history_host_copy (EphyHistoryHost *original)
+{
+ EphyHistoryHost *host;
+
+ if (original == NULL)
+ return NULL;
+
+ host = ephy_history_host_new (original->url,
+ original->title,
+ original->visit_count);
+ host->id = original->id;
+
+ return host;
+}
+
+void
+ephy_history_host_free (EphyHistoryHost *host)
+{
+ if (host == NULL)
+ return;
+
+ g_free (host->url);
+ g_free (host->title);
+
+ g_slice_free1 (sizeof (EphyHistoryHost), host);
+}
+
+EphyHistoryURL *
+ephy_history_url_new (const char *url, const char *title, int visit_count, int typed_count, int last_visit_time, double zoom_level)
+{
+ 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;
+ history_url->zoom_level = zoom_level;
+ history_url->host = NULL;
+ 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,
+ url->zoom_level);
+ copy->id = url->id;
+ copy->host = ephy_history_host_copy (url->host);
+ return copy;
+}
+
+void
+ephy_history_url_free (EphyHistoryURL *url)
+{
+ if (url == NULL)
+ return;
+
+ g_free (url->url);
+ g_free (url->title);
+ ephy_history_host_free (url->host);
+ g_slice_free1 (sizeof (EphyHistoryURL), url);
+}
+
+GList *
+ephy_history_url_list_copy (GList *original)
+{
+ GList *new = NULL, *last;
+
+ if (original) {
+ new = last = g_list_append (NULL, ephy_history_url_copy (original->data));
+ original = original->next;
+
+ while (original) {
+ last = g_list_append (last, ephy_history_url_copy (original->data));
+ last = last->next;
+ original = original->next;
+ }
+ }
+
+ return new;
+}
+
+void
+ephy_history_url_list_free (GList *list)
+{
+ g_list_free_full (list, (GDestroyNotify) ephy_history_url_free);
+}
+
+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);
+}
+
+EphyHistoryQuery *
+ephy_history_query_copy (EphyHistoryQuery *query)
+{
+ GList *iter;
+ EphyHistoryQuery *copy = ephy_history_query_new ();
+ copy->from = query->from;
+ copy->to = query->to;
+
+ for (iter = query->substring_list; iter != NULL; iter = iter->next) {
+ copy->substring_list = g_list_prepend (copy->substring_list, g_strdup (iter->data));
+ }
+ copy->substring_list = g_list_reverse (copy->substring_list);
+
+ return copy;
+}
diff --git a/lib/history/ephy-history-types.h b/lib/history/ephy-history-types.h
new file mode 100644
index 000000000..0f50ec1f9
--- /dev/null
+++ b/lib/history/ephy-history-types.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * 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_TYPED,
+ 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 enum {
+ EPHY_HISTORY_URL_TITLE,
+ EPHY_HISTORY_URL_ZOOM_LEVEL
+} EphyHistoryURLProperty;
+
+typedef struct
+{
+ int id;
+ char* url;
+ char* title;
+ int visit_count;
+} EphyHistoryHost;
+
+typedef struct _EphyHistoryURL
+{
+ int id;
+ char* url;
+ char* title;
+ int visit_count;
+ int typed_count;
+ int last_visit_time;
+ double zoom_level;
+ EphyHistoryHost *host;
+} 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);
+
+EphyHistoryHost * ephy_history_host_new (const char *url, const char *title, int visit_count);
+EphyHistoryHost * ephy_history_host_copy (EphyHistoryHost *original);
+void ephy_history_host_free (EphyHistoryHost *host);
+
+EphyHistoryURL * ephy_history_url_new (const char *url, const char* title, int visit_count, int typed_count, int last_visit_time, double zoom_level);
+EphyHistoryURL * ephy_history_url_copy (EphyHistoryURL *url);
+void ephy_history_url_free (EphyHistoryURL *url);
+
+GList * ephy_history_url_list_copy (GList *original);
+void ephy_history_url_list_free (GList *list);
+
+EphyHistoryQuery * ephy_history_query_new (void);
+void ephy_history_query_free (EphyHistoryQuery *query);
+EphyHistoryQuery * ephy_history_query_copy (EphyHistoryQuery *query);
+
+G_END_DECLS
+
+#endif /* EPHY_HISTORY_TYPES_H */
diff --git a/src/Makefile.am b/src/Makefile.am
index d6bf77fab..9e48a4645 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -147,6 +147,7 @@ epiphany_LDADD = \
$(top_builddir)/embed/libephyembed.la \
$(top_builddir)/lib/widgets/libephywidgets.la \
$(top_builddir)/lib/libephymisc.la \
+ $(top_builddir)/lib/history/libephyhistory.la \
$(top_builddir)/lib/egg/libegg.la \
$(DEPENDENCIES_LIBS) \
$(LIBINTL)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1a0d2cc26..addca34e3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,6 +2,7 @@ noinst_PROGRAMS = \
test-ephy-download \
test-ephy-embed-single \
test-ephy-embed-utils \
+ test-ephy-history \
test-ephy-location-entry \
test-ephy-search-entry \
test-ephy-sqlite \
@@ -81,6 +82,7 @@ check-local: test-nonrecursive
INCLUDES = \
-I$(top_srcdir)/embed \
-I$(top_srcdir)/lib \
+ -I$(top_srcdir)/lib/history \
-I$(top_srcdir)/lib/widgets \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/bookmarks
@@ -94,6 +96,7 @@ LDADD = \
$(top_builddir)/src/bookmarks/libephybookmarks.la \
$(top_builddir)/embed/libephyembed.la \
$(top_builddir)/lib/widgets/libephywidgets.la \
+ $(top_builddir)/lib/history/libephyhistory.la \
$(top_builddir)/lib/libephymisc.la \
$(top_builddir)/lib/egg/libegg.la \
$(DBUS_LIBS) \
@@ -115,6 +118,9 @@ test_ephy_embed_single_SOURCES = \
test_ephy_embed_utils_SOURCES = \
ephy-embed-utils-test.c
+test_ephy_history_SOURCES = \
+ ephy-history.c
+
test_ephy_location_entry_SOURCES = \
ephy-location-entry-test.c
diff --git a/tests/ephy-history.c b/tests/ephy-history.c
new file mode 100644
index 000000000..0f2d2822c
--- /dev/null
+++ b/tests/ephy-history.c
@@ -0,0 +1,324 @@
+/* vim: set sw=2 ts=2 sts=2 et: */
+/*
+ * ephy-sqlite-statement.c
+ * This file is part of Epiphany
+ *
+ * Copyright © 2010 Igalia S.L.
+ *
+ * Epiphany 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 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany 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 Epiphany; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "ephy-history-service.h"
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+static EphyHistoryService *
+ensure_empty_history (const char* filename)
+{
+ if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
+ g_unlink (filename);
+
+ return ephy_history_service_new (filename);
+}
+
+static void
+test_create_history_service (void)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history (temporary_file);
+
+ g_free (temporary_file);
+ g_object_unref (service);
+}
+
+static gboolean
+destroy_history_service_and_end_main_loop (EphyHistoryService *service)
+{
+ g_object_unref (service);
+ g_assert (TRUE);
+ gtk_main_quit ();
+
+ return FALSE;
+}
+
+static void
+test_create_history_service_and_destroy_later (void)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history (temporary_file);
+ g_free (temporary_file);
+ g_timeout_add (100, (GSourceFunc) destroy_history_service_and_end_main_loop, service);
+
+ gtk_main ();
+}
+
+static void
+page_vist_created (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ g_object_unref (service);
+ g_assert (result_data == NULL);
+ g_assert (user_data == NULL);
+ g_assert (success);
+ gtk_main_quit ();
+}
+
+static void
+test_create_history_entry (void)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history(temporary_file);
+
+ EphyHistoryPageVisit *visit = ephy_history_page_visit_new ("http://www.gnome.org", 0, EPHY_PAGE_VISIT_TYPED);
+ ephy_history_service_add_visit (service, visit, page_vist_created, NULL);
+ ephy_history_page_visit_free (visit);
+ g_free (temporary_file);
+
+ gtk_main ();
+}
+
+static GList *
+create_test_page_visit_list ()
+{
+ GList *visits = NULL;
+ int i;
+ for (i = 0; i < 100; i++) {
+ visits = g_list_append (visits, ephy_history_page_visit_new ("http://www.gnome.org", 3, EPHY_PAGE_VISIT_TYPED));
+ visits = g_list_append (visits, ephy_history_page_visit_new ("http://www.gnome.org", 5, EPHY_PAGE_VISIT_TYPED));
+ visits = g_list_append (visits, ephy_history_page_visit_new ("http://www.cuteoverload.com", 7, EPHY_PAGE_VISIT_TYPED));
+ visits = g_list_append (visits, ephy_history_page_visit_new ("http://www.cuteoverload.com", 8, EPHY_PAGE_VISIT_TYPED));
+ }
+ return visits;
+}
+
+static void
+verify_create_history_entry_cb (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ GList *visits = (GList *) result_data;
+ GList *baseline_visits = create_test_page_visit_list ();
+ GList *current = visits;
+ GList *current_baseline = baseline_visits;
+
+ g_assert (user_data == NULL);
+ g_assert (success);
+ g_assert (visits != NULL);
+ g_assert_cmpint (g_list_length (visits), ==, g_list_length (baseline_visits));
+
+ while (current_baseline) {
+ EphyHistoryPageVisit *visit, *baseline_visit;
+
+ g_assert (current);
+ visit = (EphyHistoryPageVisit *) current->data;
+ baseline_visit = (EphyHistoryPageVisit *) current_baseline->data;
+
+ g_assert_cmpstr (visit->url->url, ==, baseline_visit->url->url);
+ g_assert_cmpstr (visit->url->title, ==, baseline_visit->url->title);
+ g_assert_cmpint (visit->visit_time, ==, baseline_visit->visit_time);
+ g_assert_cmpint (visit->visit_type, ==, baseline_visit->visit_type);
+
+ current = current->next;
+ current_baseline = current_baseline->next;
+ }
+
+ ephy_history_page_visit_list_free (visits);
+ ephy_history_page_visit_list_free (baseline_visits);
+
+ g_object_unref (service);
+ gtk_main_quit ();
+}
+
+static void
+verify_create_history_entry (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ g_assert (result_data == NULL);
+ g_assert_cmpint (42, ==, GPOINTER_TO_INT(user_data));
+ g_assert (success);
+ ephy_history_service_find_visits_in_time (service, 0, 8, verify_create_history_entry_cb, NULL);
+}
+
+static void
+test_create_history_entries (void)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history(temporary_file);
+
+ GList *visits = create_test_page_visit_list ();
+
+ /* We use 42 here just to verify that user_data is passed properly to the callback */
+ ephy_history_service_add_visits (service, visits, verify_create_history_entry, GINT_TO_POINTER(42));
+ ephy_history_page_visit_list_free (visits);
+ g_free (temporary_file);
+
+ gtk_main ();
+}
+
+static void
+get_url (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ EphyHistoryURL *url = (EphyHistoryURL *) result_data;
+
+ g_assert (success == TRUE);
+ g_assert (url != NULL);
+ g_assert_cmpstr (url->title, ==, "GNOME");
+
+ ephy_history_url_free (url);
+ g_object_unref (service);
+ gtk_main_quit();
+}
+
+static void
+set_url_title (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ gboolean test_result = GPOINTER_TO_INT (user_data);
+ g_assert (success == TRUE);
+
+ if (test_result == FALSE) {
+ g_object_unref (service);
+ gtk_main_quit ();
+ } else
+ ephy_history_service_get_url (service, "http://www.gnome.org", get_url, NULL);
+}
+
+static void
+set_url_title_visit_created (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ ephy_history_service_set_url_title (service, "http://www.gnome.org", "GNOME", set_url_title, user_data);
+}
+
+static void
+test_set_url_title_helper (gboolean test_results)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history(temporary_file);
+
+ EphyHistoryPageVisit *visit = ephy_history_page_visit_new ("http://www.gnome.org", 0, EPHY_PAGE_VISIT_TYPED);
+ ephy_history_service_add_visit (service, visit, set_url_title_visit_created, GINT_TO_POINTER (test_results));
+ ephy_history_page_visit_free (visit);
+ g_free (temporary_file);
+
+ gtk_main ();
+}
+
+static void
+test_set_url_title (void)
+{
+ test_set_url_title_helper (FALSE);
+}
+
+static void
+test_set_url_title_is_correct (void)
+{
+ test_set_url_title_helper (TRUE);
+}
+
+static void
+set_url_title_url_not_existent (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ g_assert (success == FALSE);
+ g_object_unref (service);
+ gtk_main_quit ();
+}
+
+static void
+test_set_url_title_url_not_existent (void)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history(temporary_file);
+ g_free (temporary_file);
+
+ ephy_history_service_set_url_title (service, "http://www.gnome.org", "GNOME", set_url_title_url_not_existent, NULL);
+
+ gtk_main();
+}
+
+static void
+test_get_url_done (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ EphyHistoryURL *url;
+ gboolean expected_success = GPOINTER_TO_INT (user_data);
+
+ url = (EphyHistoryURL *)result_data;
+
+ g_assert (success == expected_success);
+
+ if (expected_success == TRUE) {
+ g_assert (url != NULL);
+ g_assert_cmpstr (url->url, ==, "http://www.gnome.org");
+ g_assert_cmpint (url->id, !=, -1);
+ ephy_history_url_free (url);
+ } else
+ g_assert (url == NULL);
+
+ g_object_unref (service);
+ gtk_main_quit ();
+}
+
+static void
+test_get_url_visit_added (EphyHistoryService *service, gboolean success, gpointer result_data, gpointer user_data)
+{
+ g_assert (success == TRUE);
+
+ ephy_history_service_get_url (service, "http://www.gnome.org", test_get_url_done, user_data);
+}
+
+static void
+test_get_url_helper (gboolean add_entry)
+{
+ gchar *temporary_file = g_build_filename (g_get_tmp_dir (), "epiphany-history-test.db", NULL);
+ EphyHistoryService *service = ensure_empty_history(temporary_file);
+ g_free (temporary_file);
+
+ if (add_entry == TRUE) {
+ EphyHistoryPageVisit *visit = ephy_history_page_visit_new ("http://www.gnome.org", 0, EPHY_PAGE_VISIT_TYPED);
+ ephy_history_service_add_visit (service, visit, test_get_url_visit_added, GINT_TO_POINTER (add_entry));
+ ephy_history_page_visit_free (visit);
+ } else
+ ephy_history_service_get_url (service, "http://www.gnome.org", test_get_url_done, GINT_TO_POINTER (add_entry));
+
+ gtk_main();
+}
+
+static void
+test_get_url (void)
+{
+ test_get_url_helper (TRUE);
+}
+
+static void
+test_get_url_not_existent (void)
+{
+ test_get_url_helper (FALSE);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gtk_test_init (&argc, &argv);
+
+ g_test_add_func ("/embed/history/test_create_history_service", test_create_history_service);
+ g_test_add_func ("/embed/history/test_create_history_service_and_destroy_later", test_create_history_service_and_destroy_later);
+ g_test_add_func ("/embed/history/test_create_history_entry", test_create_history_entry);
+ g_test_add_func ("/embed/history/test_create_history_entries", test_create_history_entries);
+ g_test_add_func ("/embed/history/test_set_url_title", test_set_url_title);
+ g_test_add_func ("/embed/history/test_set_url_title_is_correct", test_set_url_title_is_correct);
+ g_test_add_func ("/embed/history/test_set_url_title_url_not_existent", test_set_url_title_url_not_existent);
+ g_test_add_func ("/embed/history/test_get_url", test_get_url);
+ g_test_add_func ("/embed/history/test_get_url_not_existent", test_get_url_not_existent);
+
+ return g_test_run ();
+}