summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@gnome.org>2009-07-06 20:17:20 +0200
committerBenjamin Otte <otte@gnome.org>2009-07-06 20:17:20 +0200
commit4883644f45b3a8155b090c6d08d7e2bece89d068 (patch)
tree9860811f2e1ea70b87ba1583b8c8812370768550
parent62cb83c59ae65338818f1382ac1aadfc30b8d576 (diff)
add code to asynchronously query a database
-rw-r--r--Makefile9
-rw-r--r--ephy-history.c339
-rw-r--r--ephy-history.h80
-rw-r--r--ephy-query-history.c77
4 files changed, 488 insertions, 17 deletions
diff --git a/Makefile b/Makefile
index 6120b5c..d874ea2 100644
--- a/Makefile
+++ b/Makefile
@@ -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;
}