diff options
author | Xan Lopez <xlopez@igalia.com> | 2011-11-25 20:39:50 +0800 |
---|---|---|
committer | Xan Lopez <xan@igalia.com> | 2012-03-07 04:49:42 +0800 |
commit | 374d66dd260b989f22eba25dad4fabc49da2d586 (patch) | |
tree | d32121ea4f1769a3a725c75f1cf01229cb533f95 | |
parent | 77ee0fdf4c5383f2134608e741b73f51ef30f430 (diff) | |
download | gsoc2013-epiphany-374d66dd260b989f22eba25dad4fabc49da2d586.tar.gz 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.ac | 1 | ||||
-rw-r--r-- | lib/Makefile.am | 2 | ||||
-rw-r--r-- | lib/history/ephy-history-service-hosts-table.c | 316 | ||||
-rw-r--r-- | lib/history/ephy-history-service-private.h | 55 | ||||
-rw-r--r-- | lib/history/ephy-history-service-urls-table.c | 371 | ||||
-rw-r--r-- | lib/history/ephy-history-service-visits-table.c | 207 | ||||
-rw-r--r-- | lib/history/ephy-history-service.c | 742 | ||||
-rw-r--r-- | lib/history/ephy-history-service.h | 70 | ||||
-rw-r--r-- | lib/history/ephy-history-types.c | 218 | ||||
-rw-r--r-- | lib/history/ephy-history-types.h | 106 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 6 | ||||
-rw-r--r-- | tests/ephy-history.c | 324 |
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 (); +} |