diff options
author | Benjamin Otte <otte@gnome.org> | 2009-07-06 20:17:20 +0200 |
---|---|---|
committer | Benjamin Otte <otte@gnome.org> | 2009-07-06 20:17:20 +0200 |
commit | 4883644f45b3a8155b090c6d08d7e2bece89d068 (patch) | |
tree | 9860811f2e1ea70b87ba1583b8c8812370768550 | |
parent | 62cb83c59ae65338818f1382ac1aadfc30b8d576 (diff) |
add code to asynchronously query a database
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | ephy-history.c | 339 | ||||
-rw-r--r-- | ephy-history.h | 80 | ||||
-rw-r--r-- | ephy-query-history.c | 77 |
4 files changed, 488 insertions, 17 deletions
@@ -1,12 +1,15 @@ APP = ephy-query-history CC = gcc -CFLAGS = -g -Wall -Werror -DEPIPHANY_COMPILATION +CFLAGS = -g -DEPIPHANY_COMPILATION -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -Wold-style-definition -Wdeclaration-after-statement -Wmissing-declarations -Wmissing-prototypes -Wredundant-decls -Wmissing-noreturn -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings -Winline -Wformat-nonliteral -Wformat-security -Wswitch-enum -Wswitch-default -Winit-self -Wmissing-include-dirs -Wundef -Waggregate-return -Wmissing-format-attribute -Wnested-externs -Wunsafe-loop-optimizations -Wpacked -Winvalid-pch -Wlogical-op -Werror -PKG = gtk+-2.0 gthread-2.0 +PKG = gtk+-2.0 gthread-2.0 sqlite3 PKG_CFLAGS = `pkg-config --cflags $(PKG)` PKG_LIBS = `pkg-config --libs $(PKG)` -SRCS = ephy-query-history.c +SRCS = \ + ephy-history.c \ + ephy-query-history.c + OBJS = $(SRCS:.c=.o) all: $(APP) diff --git a/ephy-history.c b/ephy-history.c new file mode 100644 index 0000000..34881f9 --- /dev/null +++ b/ephy-history.c @@ -0,0 +1,339 @@ +/* + * Copyright © 2009 Benjamin Otte <otte@gnome.org> + * + * 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 of the License, 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sqlite3.h> + +#include "ephy-history.h" + +#define EPHY_HISTORY_DEFAULT ".gnome2/epiphany/history.sqlite" + +#define EPHY_HISTORY_PRIORITY_SELECT G_PRIORITY_DEFAULT + +struct _EphyHistoryPrivate { + /* data only touched upon creation and inside thread */ + GMutex * lock; + char * filename; + sqlite3 * db; +}; + +enum { + PROP_0, + PROP_FILENAME +}; + +G_DEFINE_TYPE (EphyHistory, ephy_history, G_TYPE_OBJECT); + +/*** OBJECT ***/ + +static void +ephy_history_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyHistory *history = EPHY_HISTORY (object); + EphyHistoryPrivate *priv = history->priv; + const char *s; + + switch (prop_id) + { + case PROP_FILENAME: + s = g_value_get_string (value); + if (s == NULL) + s = EPHY_HISTORY_DEFAULT; + if (g_path_is_absolute (s)) + priv->filename = g_strdup (s); + else + priv->filename = g_build_filename (g_get_home_dir (), + s, + NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ephy_history_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyHistory *history = EPHY_HISTORY (object); + EphyHistoryPrivate *priv = history->priv; + + switch (prop_id) + { + case PROP_FILENAME: + g_value_set_string (value, priv->filename); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +ephy_history_finalize (GObject *object) +{ + EphyHistory *history = EPHY_HISTORY (object); + EphyHistoryPrivate *priv = history->priv; + + g_free (priv->filename); + sqlite3_close (priv->db); + g_mutex_free (priv->lock); + + G_OBJECT_CLASS (ephy_history_parent_class)->finalize (object); +} + +static void +ephy_history_class_init (EphyHistoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = ephy_history_finalize; + object_class->get_property = ephy_history_get_property; + object_class->set_property = ephy_history_set_property; + + g_object_class_install_property (object_class, + PROP_FILENAME, + g_param_spec_string ("filename", + "filename", + "name of history file", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)); + + g_type_class_add_private (object_class, sizeof (EphyHistoryPrivate)); +} + +static void +ephy_history_init (EphyHistory *history) +{ + EphyHistoryPrivate *priv; + + priv = history->priv = G_TYPE_INSTANCE_GET_PRIVATE (history, EPHY_TYPE_HISTORY, EphyHistoryPrivate); + + priv->lock = g_mutex_new (); +} + +EphyHistory * +ephy_history_new (const char *filename) +{ + return g_object_new (EPHY_TYPE_HISTORY, "filename", filename, NULL); +} + +EphyHistory * +ephy_history_get_default (void) +{ + static EphyHistory *history = NULL; + + if (G_UNLIKELY (history == NULL)) + history = ephy_history_new (NULL); + + return g_object_ref (history); +} + +static gboolean +check_sqlite_result (EphyHistory * history, + GSimpleAsyncResult *res, + int result) +{ + EphyHistoryPrivate *priv = history->priv; + + if (result == SQLITE_OK) + return TRUE; + + /* FIXME: improve */ + g_simple_async_result_set_error (res, + EPHY_SQLITE_ERROR, + sqlite3_extended_errcode (priv->db), + "%s", sqlite3_errmsg (priv->db)); + return FALSE; +} + +static gboolean +ephy_history_open_database (EphyHistory * history, + GSimpleAsyncResult *res) +{ + EphyHistoryPrivate *priv = history->priv; + + if (priv->db) + return TRUE; + + if (!check_sqlite_result (history, res, + sqlite3_open_v2 (priv->filename, + &priv->db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL))) + return FALSE; + + /* FIXME: create database if it doesn't exist */ + return TRUE; +} + +GQuark +ephy_sqlite_error_quark (void) +{ + GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_static_string ("epiphany-sqlite-error"); + + return quark; +} + +/*** SELECT ***/ + +typedef struct { + guint initial_results; + guint progress_ms; + char *sql; + GSList *results; +} SelectData; + +static void +select_data_free (gpointer data) +{ + SelectData *select = data; + + if (select->sql) + sqlite3_free (select->sql); + + g_slist_foreach (select->results, (GFunc) g_value_array_free, NULL); + g_slist_free (select->results); + g_slice_free (SelectData, select); +} + +static void +ephy_history_select_thread (GSimpleAsyncResult *res, + GObject * object, + GCancellable * cancellable) +{ + EphyHistory *history = EPHY_HISTORY (object); + EphyHistoryPrivate *priv = history->priv; + SelectData *select = g_simple_async_result_get_op_res_gpointer (res); + sqlite3_stmt *stmt; + + g_mutex_lock (priv->lock); + + if (!ephy_history_open_database (history, res) || + !check_sqlite_result (history, res, + sqlite3_prepare_v2 (priv->db, + select->sql, + -1, + &stmt, + NULL))) /* we only run one SQL */ + goto out; + + while (sqlite3_step (stmt) == SQLITE_ROW) + { + guint i, n_rows = sqlite3_data_count (stmt); + GValueArray *array = g_value_array_new (n_rows); + + for (i = 0; i < n_rows; i++) + { + g_value_array_append (array, NULL); + switch (sqlite3_column_type (stmt, i)) + { + case SQLITE_INTEGER: + g_value_init (&array->values[i], G_TYPE_INT64); + g_value_set_int64 (&array->values[i], sqlite3_column_int64 (stmt, i)); + break; + case SQLITE_TEXT: + g_value_init (&array->values[i], G_TYPE_STRING); + g_value_set_string (&array->values[i], (char *) sqlite3_column_text (stmt, i)); + break; + case SQLITE_FLOAT: + g_value_init (&array->values[i], G_TYPE_DOUBLE); + g_value_set_int64 (&array->values[i], sqlite3_column_double (stmt, i)); + break; + case SQLITE_BLOB: + case SQLITE_NULL: + /* FIXME: implement */ + default: + g_warning ("unhandled column type %d", sqlite3_column_type (stmt, i)); + break; + } + } + select->results = g_slist_prepend (select->results, array); + } + + check_sqlite_result (history, res, sqlite3_finalize (stmt)); + +out: + g_mutex_unlock (priv->lock); + g_simple_async_result_complete_in_idle (res); +} + +void +ephy_history_select_async (EphyHistory * history, + guint initial_results, + guint progrss_ms, + GAsyncReadyCallback callback, + gpointer user_data, + GCancellable * cancellable, + const char * sql, + ...) +{ + GSimpleAsyncResult *res; + SelectData *select; + va_list args; + + g_return_if_fail (EPHY_IS_HISTORY (history)); + g_return_if_fail (callback); + g_return_if_fail (sql != NULL); + + select = g_slice_new0 (SelectData); + va_start (args, sql); + select->sql = sqlite3_vmprintf (sql, args); + va_end (args); + + res = g_simple_async_result_new (G_OBJECT (history), callback, user_data, ephy_history_select_async); + g_simple_async_result_set_op_res_gpointer (res, select, select_data_free); + + g_simple_async_result_run_in_thread (res, ephy_history_select_thread, EPHY_HISTORY_PRIORITY_SELECT, cancellable); + g_object_unref (res); +} + +GSList * +ephy_history_select_finish (EphyHistory * history, + GAsyncResult *res, + GError ** error) +{ + GSimpleAsyncResult *simple; + SelectData *select; + + g_return_val_if_fail (EPHY_IS_HISTORY (history), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (res); + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ephy_history_select_async); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + select = g_simple_async_result_get_op_res_gpointer (simple); + return select->results; +} diff --git a/ephy-history.h b/ephy-history.h new file mode 100644 index 0000000..da7114f --- /dev/null +++ b/ephy-history.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2009 Benjamin Otte <otte@gnome.org> + * + * 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. + * + */ + +#if !defined (__EPHY_EPIPHANY_H_INSIDE__) && !defined (EPIPHANY_COMPILATION) +#error "Only <epiphany/epiphany.h> can be included directly." +#endif + +#ifndef EPHY_HISTORY_H +#define EPHY_HISTORY_H + +#include <gio/gio.h> + +G_BEGIN_DECLS + + +#define EPHY_SQLITE_ERROR (ephy_sqlite_error_quark()) + +#define EPHY_TYPE_HISTORY (ephy_history_get_type ()) +#define EPHY_HISTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_HISTORY, EphyHistory)) +#define EPHY_HISTORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_HISTORY, EphyHistoryClass)) +#define EPHY_IS_HISTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_HISTORY)) +#define EPHY_IS_HISTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_HISTORY)) +#define EPHY_HISTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_HISTORY, EphyHistoryClass)) + +typedef struct _EphyHistory EphyHistory; +typedef struct _EphyHistoryClass EphyHistoryClass; +typedef struct _EphyHistoryPrivate EphyHistoryPrivate; + +struct _EphyHistory +{ + GObject parent; + + /*< private >*/ + EphyHistoryPrivate *priv; +}; + +struct _EphyHistoryClass +{ + GObjectClass parent_class; +}; + +GType ephy_history_get_type (void); + +GQuark ephy_sqlite_error_quark (void); + +EphyHistory * ephy_history_new (const char * filename); +EphyHistory * ephy_history_get_default (void); + +void ephy_history_select_async (EphyHistory * history, + guint initial_results, + guint progrss_ms, + GAsyncReadyCallback callback, + gpointer user_data, + GCancellable * cancellable, + const char * sql, + ...); /* no G_GNUC_PRINTF because it takes %Q args */ +GSList * ephy_history_select_finish (EphyHistory * history, + GAsyncResult * res, + GError ** error); + + +G_END_DECLS + +#endif diff --git a/ephy-query-history.c b/ephy-query-history.c index ab00443..203ca74 100644 --- a/ephy-query-history.c +++ b/ephy-query-history.c @@ -22,36 +22,85 @@ #endif #include <glib.h> +#include "ephy-history.h" + +static void +ephy_query_history_print_results (GObject *history, GAsyncResult *res, gpointer loop) +{ + GError *error = NULL; + GSList *list; + guint i; + + g_main_loop_quit (loop); + + list = ephy_history_select_finish (EPHY_HISTORY (history), res, &error); + if (error) + { + g_print ("Error: %s\n", error->message); + g_error_free (error); + return; + } + + for (; list; list = list->next) + { + GValueArray *array = list->data; + for (i = 0; i < array->n_values; i++) + { + char *s = g_strdup_value_contents (&array->values[i]); + g_print ("%s\t", s); + } + g_print ("\n"); + } +} int main (int argc, char *argv[]) { - - GOptionEntry options[] = { + static char *filename = NULL; + static GOptionEntry options[] = { + { "filename", 'f', 0, G_OPTION_ARG_FILENAME, &filename, "file to use as history instead of default", "FILENAME" }, { NULL } }; GOptionContext *ctx; GError *error = NULL; + EphyHistory *history; + GMainLoop *loop; g_thread_init (NULL); + g_type_init (); ctx = g_option_context_new (""); g_option_context_add_main_entries (ctx, options, "options"); g_option_context_parse (ctx, &argc, &argv, &error); g_option_context_free (ctx); - if (error) { - g_printerr ("Error parsing command line arguments: %s\n", error->message); - g_error_free (error); - return 1; - } - -#if 0 - if (argc < 2) { - g_printerr ("Usage: %s [OPTIONS] filename\n", argv[0]); - return 1; - } -#endif + if (error) + { + g_printerr ("Error parsing command line arguments: %s\n", error->message); + g_error_free (error); + return 1; + } + + if (argc < 2) + { + g_print ("usage: %s [OPTIONS] SQL\n", argv[0]); + return 1; + } + + history = ephy_history_new (filename); + loop = g_main_loop_new (NULL, FALSE); + + ephy_history_select_async (history, + 50, + 1000, + ephy_query_history_print_results, + loop, + NULL, + argv[1]); + + g_main_loop_run (loop); + g_main_loop_unref (loop); + g_object_unref (history); return 0; } |