/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2013 Intel Corporation
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Tristan Van Berkom
*/
/**
* SECTION: e-data-book-cursor-sqlite
* @include: libedata-book/libedata-book.h
* @short_description: The SQLite cursor implementation
*
* This cursor implementation can be used with any backend which
* stores contacts using #EBookSqlite.
*/
#include "evolution-data-server-config.h"
#include
#include "e-data-book-cursor-sqlite.h"
#define E_DATA_BOOK_CURSOR_SQLITE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_DATA_BOOK_CURSOR_SQLITE, EDataBookCursorSqlitePrivate))
/* GObjectClass */
static void e_data_book_cursor_sqlite_dispose (GObject *object);
static void e_data_book_cursor_sqlite_finalize (GObject *object);
static void e_data_book_cursor_sqlite_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
/* EDataBookCursorClass */
static gboolean e_data_book_cursor_sqlite_set_sexp (EDataBookCursor *cursor,
const gchar *sexp,
GError **error);
static gint e_data_book_cursor_sqlite_step (EDataBookCursor *cursor,
const gchar *revision_guard,
EBookCursorStepFlags flags,
EBookCursorOrigin origin,
gint count,
GSList **results,
GCancellable *cancellable,
GError **error);
static gboolean e_data_book_cursor_sqlite_set_alphabetic_index (EDataBookCursor *cursor,
gint index,
const gchar *locale,
GError **error);
static gboolean e_data_book_cursor_sqlite_get_position (EDataBookCursor *cursor,
gint *total,
gint *position,
GCancellable *cancellable,
GError **error);
static gint e_data_book_cursor_sqlite_compare_contact (EDataBookCursor *cursor,
EContact *contact,
gboolean *matches_sexp);
static gboolean e_data_book_cursor_sqlite_load_locale (EDataBookCursor *cursor,
gchar **locale,
GError **error);
struct _EDataBookCursorSqlitePrivate {
EBookSqlite *ebsql;
EbSqlCursor *cursor;
gchar *revision_key;
};
enum {
PROP_0,
PROP_EBSQL,
PROP_REVISION_KEY,
PROP_CURSOR,
};
G_DEFINE_TYPE (EDataBookCursorSqlite, e_data_book_cursor_sqlite, E_TYPE_DATA_BOOK_CURSOR);
/************************************************
* GObjectClass *
************************************************/
static void
e_data_book_cursor_sqlite_class_init (EDataBookCursorSqliteClass *class)
{
GObjectClass *object_class;
EDataBookCursorClass *cursor_class;
object_class = G_OBJECT_CLASS (class);
object_class->dispose = e_data_book_cursor_sqlite_dispose;
object_class->finalize = e_data_book_cursor_sqlite_finalize;
object_class->set_property = e_data_book_cursor_sqlite_set_property;
cursor_class = E_DATA_BOOK_CURSOR_CLASS (class);
cursor_class->set_sexp = e_data_book_cursor_sqlite_set_sexp;
cursor_class->step = e_data_book_cursor_sqlite_step;
cursor_class->set_alphabetic_index = e_data_book_cursor_sqlite_set_alphabetic_index;
cursor_class->get_position = e_data_book_cursor_sqlite_get_position;
cursor_class->compare_contact = e_data_book_cursor_sqlite_compare_contact;
cursor_class->load_locale = e_data_book_cursor_sqlite_load_locale;
g_object_class_install_property (
object_class,
PROP_EBSQL,
g_param_spec_object (
"ebsql", "EBookSqlite",
"The EBookSqlite to use for queries",
E_TYPE_BOOK_SQLITE,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (
object_class,
PROP_REVISION_KEY,
g_param_spec_string (
"revision-key", "Revision Key",
"The key name to fetch the revision from the sqlite backend",
NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (
object_class,
PROP_CURSOR,
g_param_spec_pointer (
"cursor", "Cursor",
"The EbSqlCursor pointer",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (class, sizeof (EDataBookCursorSqlitePrivate));
}
static void
e_data_book_cursor_sqlite_init (EDataBookCursorSqlite *cursor)
{
cursor->priv = E_DATA_BOOK_CURSOR_SQLITE_GET_PRIVATE (cursor);
}
static void
e_data_book_cursor_sqlite_dispose (GObject *object)
{
EDataBookCursorSqlite *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
EDataBookCursorSqlitePrivate *priv = cursor->priv;
if (priv->ebsql != NULL) {
if (priv->cursor != NULL)
e_book_sqlite_cursor_free (
priv->ebsql, priv->cursor);
g_object_unref (priv->ebsql);
priv->ebsql = NULL;
priv->cursor = NULL;
}
G_OBJECT_CLASS (e_data_book_cursor_sqlite_parent_class)->dispose (object);
}
static void
e_data_book_cursor_sqlite_finalize (GObject *object)
{
EDataBookCursorSqlite *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
EDataBookCursorSqlitePrivate *priv = cursor->priv;
g_free (priv->revision_key);
G_OBJECT_CLASS (e_data_book_cursor_sqlite_parent_class)->finalize (object);
}
static void
e_data_book_cursor_sqlite_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
EDataBookCursorSqlite *cursor = E_DATA_BOOK_CURSOR_SQLITE (object);
EDataBookCursorSqlitePrivate *priv = cursor->priv;
switch (property_id) {
case PROP_EBSQL:
/* Construct-only, can only be set once */
priv->ebsql = g_value_dup_object (value);
break;
case PROP_REVISION_KEY:
/* Construct-only, can only be set once */
priv->revision_key = g_value_dup_string (value);
break;
case PROP_CURSOR:
/* Construct-only, can only be set once */
priv->cursor = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/************************************************
* EDataBookCursorClass *
************************************************/
static gboolean
e_data_book_cursor_sqlite_set_sexp (EDataBookCursor *cursor,
const gchar *sexp,
GError **error)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
GError *local_error = NULL;
gboolean success;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
success = e_book_sqlite_cursor_set_sexp (
priv->ebsql, priv->cursor, sexp, &local_error);
if (!success) {
if (g_error_matches (local_error,
E_BOOK_SQLITE_ERROR,
E_BOOK_SQLITE_ERROR_INVALID_QUERY)) {
g_set_error_literal (
error,
E_CLIENT_ERROR,
E_CLIENT_ERROR_INVALID_QUERY,
local_error->message);
g_clear_error (&local_error);
} else {
g_propagate_error (error, local_error);
}
}
return success;
}
static gboolean
convert_origin (EBookCursorOrigin src_origin,
EbSqlCursorOrigin *dest_origin,
GError **error)
{
gboolean success = TRUE;
switch (src_origin) {
case E_BOOK_CURSOR_ORIGIN_CURRENT:
*dest_origin = EBSQL_CURSOR_ORIGIN_CURRENT;
break;
case E_BOOK_CURSOR_ORIGIN_BEGIN:
*dest_origin = EBSQL_CURSOR_ORIGIN_BEGIN;
break;
case E_BOOK_CURSOR_ORIGIN_END:
*dest_origin = EBSQL_CURSOR_ORIGIN_END;
break;
default:
success = FALSE;
g_set_error_literal (
error,
E_CLIENT_ERROR,
E_CLIENT_ERROR_INVALID_ARG,
_("Unrecognized cursor origin"));
break;
}
return success;
}
static void
convert_flags (EBookCursorStepFlags src_flags,
EbSqlCursorStepFlags *dest_flags)
{
if (src_flags & E_BOOK_CURSOR_STEP_MOVE)
*dest_flags |= EBSQL_CURSOR_STEP_MOVE;
if (src_flags & E_BOOK_CURSOR_STEP_FETCH)
*dest_flags |= EBSQL_CURSOR_STEP_FETCH;
}
static gint
e_data_book_cursor_sqlite_step (EDataBookCursor *cursor,
const gchar *revision_guard,
EBookCursorStepFlags flags,
EBookCursorOrigin origin,
gint count,
GSList **results,
GCancellable *cancellable,
GError **error)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
GSList *local_results = NULL, *local_converted_results = NULL, *l;
EbSqlCursorOrigin sqlite_origin = EBSQL_CURSOR_ORIGIN_CURRENT;
EbSqlCursorStepFlags sqlite_flags = 0;
gchar *revision = NULL;
gboolean success = FALSE;
gint n_results = -1;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
if (!convert_origin (origin, &sqlite_origin, error))
return FALSE;
convert_flags (flags, &sqlite_flags);
/* Here we check the EBookSqlite revision
* against the revision_guard with an atomic transaction
* with the sqlite.
*
* The addressbook modifications and revision changes
* are also atomically committed to the SQLite.
*/
success = e_book_sqlite_lock (priv->ebsql, EBSQL_LOCK_READ, cancellable, error);
if (success && revision_guard)
success = e_book_sqlite_get_key_value (
priv->ebsql,
priv->revision_key,
&revision,
error);
if (success && revision_guard &&
g_strcmp0 (revision, revision_guard) != 0) {
g_set_error_literal (
error,
E_CLIENT_ERROR,
E_CLIENT_ERROR_OUT_OF_SYNC,
_("Out of sync revision while moving cursor"));
success = FALSE;
}
if (success) {
GError *local_error = NULL;
n_results = e_book_sqlite_cursor_step (
priv->ebsql,
priv->cursor,
sqlite_flags,
sqlite_origin,
count,
&local_results,
cancellable,
&local_error);
if (n_results < 0) {
/* Convert the SQLite backend error to an EClient error */
if (g_error_matches (local_error,
E_BOOK_SQLITE_ERROR,
E_BOOK_SQLITE_ERROR_END_OF_LIST)) {
g_set_error_literal (
error, E_CLIENT_ERROR,
E_CLIENT_ERROR_QUERY_REFUSED,
local_error->message);
g_clear_error (&local_error);
} else
g_propagate_error (error, local_error);
success = FALSE;
}
}
if (success) {
success = e_book_sqlite_unlock (priv->ebsql, EBSQL_UNLOCK_NONE, error);
} else {
GError *local_error = NULL;
if (!e_book_sqlite_unlock (priv->ebsql, EBSQL_UNLOCK_NONE, &local_error)) {
g_warning (
"Error occurred while unlocking the SQLite: %s",
local_error->message);
g_clear_error (&local_error);
}
}
for (l = local_results; l; l = l->next) {
EbSqlSearchData *data = l->data;
local_converted_results =
g_slist_prepend (local_converted_results, data->vcard);
data->vcard = NULL;
}
g_slist_free_full (local_results, (GDestroyNotify) e_book_sqlite_search_data_free);
if (results)
*results = g_slist_reverse (local_converted_results);
else
g_slist_free_full (local_converted_results, (GDestroyNotify) g_free);
g_free (revision);
if (success)
return n_results;
return -1;
}
static gboolean
e_data_book_cursor_sqlite_set_alphabetic_index (EDataBookCursor *cursor,
gint index,
const gchar *locale,
GError **error)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
gchar *current_locale = NULL;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
if (!e_book_sqlite_get_locale (priv->ebsql, ¤t_locale, error))
return FALSE;
/* Locale mismatch, need to report error */
if (g_strcmp0 (current_locale, locale) != 0) {
g_set_error_literal (
error,
E_CLIENT_ERROR,
E_CLIENT_ERROR_OUT_OF_SYNC,
_("Alphabetic index was set for incorrect locale"));
g_free (current_locale);
return FALSE;
}
e_book_sqlite_cursor_set_target_alphabetic_index (
priv->ebsql,
priv->cursor,
index);
g_free (current_locale);
return TRUE;
}
static gboolean
e_data_book_cursor_sqlite_get_position (EDataBookCursor *cursor,
gint *total,
gint *position,
GCancellable *cancellable,
GError **error)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
return e_book_sqlite_cursor_calculate (
priv->ebsql,
priv->cursor,
total, position,
cancellable,
error);
}
static gint
e_data_book_cursor_sqlite_compare_contact (EDataBookCursor *cursor,
EContact *contact,
gboolean *matches_sexp)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
return e_book_sqlite_cursor_compare_contact (
priv->ebsql,
priv->cursor,
contact,
matches_sexp);
}
static gboolean
e_data_book_cursor_sqlite_load_locale (EDataBookCursor *cursor,
gchar **locale,
GError **error)
{
EDataBookCursorSqlite *cursor_sqlite;
EDataBookCursorSqlitePrivate *priv;
cursor_sqlite = E_DATA_BOOK_CURSOR_SQLITE (cursor);
priv = cursor_sqlite->priv;
return e_book_sqlite_get_locale (priv->ebsql, locale, error);
}
/************************************************
* API *
************************************************/
/**
* e_data_book_cursor_sqlite_new:
* @backend: the #EBookBackend creating this cursor
* @ebsql: the #EBookSqlite object to base this cursor on
* @revision_key: The key name to consult for the current overall contacts database revision
* @sort_fields: (array length=n_fields): an array of #EContactFields as sort keys in order of priority
* @sort_types: (array length=n_fields): an array of #EBookCursorSortTypes, one for each field in @sort_fields
* @n_fields: the number of fields to sort results by.
* @error: a return location to story any error that might be reported.
*
* Creates an #EDataBookCursor and implements all of the cursor methods
* using the delegate @ebsql object.
*
* This is a suitable cursor type for any backend which stores its contacts
* using the #EBookSqlite object.
*
* Returns: (transfer full): A newly created #EDataBookCursor, or %NULL if cursor creation failed.
*
* Since: 3.12
*/
EDataBookCursor *
e_data_book_cursor_sqlite_new (EBookBackend *backend,
EBookSqlite *ebsql,
const gchar *revision_key,
const EContactField *sort_fields,
const EBookCursorSortType *sort_types,
guint n_fields,
GError **error)
{
EDataBookCursor *cursor = NULL;
EbSqlCursor *ebsql_cursor;
GError *local_error = NULL;
g_return_val_if_fail (E_IS_BOOK_BACKEND (backend), NULL);
g_return_val_if_fail (E_IS_BOOK_SQLITE (ebsql), NULL);
ebsql_cursor = e_book_sqlite_cursor_new (
ebsql, NULL,
sort_fields,
sort_types,
n_fields,
&local_error);
if (ebsql_cursor) {
cursor = g_object_new (
E_TYPE_DATA_BOOK_CURSOR_SQLITE,
"backend", backend,
"ebsql", ebsql,
"revision-key", revision_key,
"cursor", ebsql_cursor,
NULL);
/* Initially created cursors should have a position & total */
if (!e_data_book_cursor_load_locale (E_DATA_BOOK_CURSOR (cursor),
NULL, NULL, error))
g_clear_object (&cursor);
} else if (g_error_matches (local_error,
E_BOOK_SQLITE_ERROR,
E_BOOK_SQLITE_ERROR_INVALID_QUERY)) {
g_set_error_literal (
error,
E_CLIENT_ERROR,
E_CLIENT_ERROR_INVALID_QUERY,
local_error->message);
g_clear_error (&local_error);
} else {
g_propagate_error (error, local_error);
}
return cursor;
}