/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* 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: Chris Toshok
*/
/**
* SECTION: e-book-backend-summary
* @include: libedata-book/libedata-book.h
* @short_description: A utility for storing contact data and searching for contacts
*
* The #EBookBackendSummary is deprecated, use #EBookSqlite instead.
*/
#include "evolution-data-server-config.h"
#include
#include
#include
#include
#include
#include
#include
#include "e-book-backend-summary.h"
struct _EBookBackendSummaryPrivate {
gchar *summary_path;
FILE *fp;
guint32 file_version;
time_t mtime;
gboolean upgraded;
gboolean dirty;
gint flush_timeout_millis;
gint flush_timeout;
GPtrArray *items;
GHashTable *id_to_item;
guint32 num_items; /* used only for loading */
#ifdef SUMMARY_STATS
gint size;
#endif
};
typedef struct {
gchar *id;
gchar *nickname;
gchar *full_name;
gchar *given_name;
gchar *surname;
gchar *file_as;
gchar *email_1;
gchar *email_2;
gchar *email_3;
gchar *email_4;
gboolean wants_html;
gboolean wants_html_set;
gboolean list;
gboolean list_show_addresses;
} EBookBackendSummaryItem;
typedef struct {
/* these lengths do *not* including the terminating \0, as
* it's not stored on disk. */
guint16 id_len;
guint16 nickname_len;
guint16 full_name_len; /* version 3.0 field */
guint16 given_name_len;
guint16 surname_len;
guint16 file_as_len;
guint16 email_1_len;
guint16 email_2_len;
guint16 email_3_len;
guint16 email_4_len;
guint8 wants_html;
guint8 wants_html_set;
guint8 list;
guint8 list_show_addresses;
} EBookBackendSummaryDiskItem;
typedef struct {
guint32 file_version;
guint32 num_items;
guint32 summary_mtime; /* version 2.0 field */
} EBookBackendSummaryHeader;
G_DEFINE_TYPE_WITH_PRIVATE (EBookBackendSummary, e_book_backend_summary, G_TYPE_OBJECT)
#define PAS_SUMMARY_MAGIC "PAS-SUMMARY"
#define PAS_SUMMARY_MAGIC_LEN 11
#define PAS_SUMMARY_FILE_VERSION_1_0 1000
#define PAS_SUMMARY_FILE_VERSION_2_0 2000
#define PAS_SUMMARY_FILE_VERSION_3_0 3000
#define PAS_SUMMARY_FILE_VERSION_4_0 4000
#define PAS_SUMMARY_FILE_VERSION_5_0 5000
#define PAS_SUMMARY_FILE_VERSION PAS_SUMMARY_FILE_VERSION_5_0
static void
free_summary_item (EBookBackendSummaryItem *item)
{
g_free (item->id);
g_free (item->nickname);
g_free (item->full_name);
g_free (item->given_name);
g_free (item->surname);
g_free (item->file_as);
g_free (item->email_1);
g_free (item->email_2);
g_free (item->email_3);
g_free (item->email_4);
g_free (item);
}
static void
clear_items (EBookBackendSummary *summary)
{
gint i;
gint num = summary->priv->items->len;
for (i = 0; i < num; i++) {
EBookBackendSummaryItem *item = g_ptr_array_remove_index_fast (summary->priv->items, 0);
if (item) {
g_hash_table_remove (summary->priv->id_to_item, item->id);
free_summary_item (item);
}
}
}
/**
* e_book_backend_summary_new:
* @summary_path: a local file system path
* @flush_timeout_millis: a flush interval, in milliseconds
*
* Creates an #EBookBackendSummary object without loading it
* or otherwise affecting the file. @flush_timeout_millis
* specifies how much time should elapse, at a minimum, from
* the summary is changed until it is flushed to disk.
*
* Returns: A new #EBookBackendSummary.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
EBookBackendSummary *
e_book_backend_summary_new (const gchar *summary_path,
gint flush_timeout_millis)
{
EBookBackendSummary *summary = g_object_new (E_TYPE_BOOK_BACKEND_SUMMARY, NULL);
summary->priv->summary_path = g_strdup (summary_path);
summary->priv->flush_timeout_millis = flush_timeout_millis;
summary->priv->file_version = PAS_SUMMARY_FILE_VERSION_4_0;
return summary;
}
static void
e_book_backend_summary_finalize (GObject *object)
{
EBookBackendSummaryPrivate *priv;
priv = E_BOOK_BACKEND_SUMMARY (object)->priv;
if (priv->fp)
fclose (priv->fp);
if (priv->dirty)
e_book_backend_summary_save (E_BOOK_BACKEND_SUMMARY (object));
else
utime (priv->summary_path, NULL);
if (priv->flush_timeout)
g_source_remove (priv->flush_timeout);
g_free (priv->summary_path);
clear_items (E_BOOK_BACKEND_SUMMARY (object));
g_ptr_array_free (priv->items, TRUE);
g_hash_table_destroy (priv->id_to_item);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_book_backend_summary_parent_class)->finalize (object);
}
static void
e_book_backend_summary_class_init (EBookBackendSummaryClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->finalize = e_book_backend_summary_finalize;
}
static void
e_book_backend_summary_init (EBookBackendSummary *summary)
{
summary->priv = e_book_backend_summary_get_instance_private (summary);
summary->priv->items = g_ptr_array_new ();
summary->priv->id_to_item = g_hash_table_new (g_str_hash, g_str_equal);
}
static gboolean
e_book_backend_summary_check_magic (EBookBackendSummary *summary,
FILE *fp)
{
gchar buf[PAS_SUMMARY_MAGIC_LEN + 1];
gint rv;
memset (buf, 0, sizeof (buf));
rv = fread (buf, PAS_SUMMARY_MAGIC_LEN, 1, fp);
if (rv != 1)
return FALSE;
if (strncmp (buf, PAS_SUMMARY_MAGIC, PAS_SUMMARY_MAGIC_LEN))
return FALSE;
return TRUE;
}
static gboolean
e_book_backend_summary_load_header (EBookBackendSummary *summary,
FILE *fp,
EBookBackendSummaryHeader *header)
{
gint rv;
rv = fread (&header->file_version, sizeof (header->file_version), 1, fp);
if (rv != 1)
return FALSE;
header->file_version = g_ntohl (header->file_version);
if (header->file_version < PAS_SUMMARY_FILE_VERSION) {
return FALSE; /* this will cause the entire summary to be rebuilt */
}
rv = fread (&header->num_items, sizeof (header->num_items), 1, fp);
if (rv != 1)
return FALSE;
header->num_items = g_ntohl (header->num_items);
rv = fread (&header->summary_mtime, sizeof (header->summary_mtime), 1, fp);
if (rv != 1)
return FALSE;
header->summary_mtime = g_ntohl (header->summary_mtime);
return TRUE;
}
static gchar *
read_string (FILE *fp,
gsize len)
{
gchar *buf;
size_t rv;
/* Avoid overflow for the nul byte. */
if (len == G_MAXSIZE)
return NULL;
buf = g_new0 (char, len + 1);
rv = fread (buf, sizeof (gchar), len, fp);
if (rv != len) {
g_free (buf);
return NULL;
}
/* Validate the string as UTF-8. */
if (!g_utf8_validate (buf, rv, NULL)) {
g_free (buf);
return NULL;
}
return buf;
}
static gboolean
e_book_backend_summary_load_item (EBookBackendSummary *summary,
EBookBackendSummaryItem **new_item)
{
EBookBackendSummaryItem *item;
gchar *buf;
FILE *fp = summary->priv->fp;
if (summary->priv->file_version >= PAS_SUMMARY_FILE_VERSION_4_0) {
EBookBackendSummaryDiskItem disk_item;
gint rv = fread (&disk_item, sizeof (disk_item), 1, fp);
if (rv != 1)
return FALSE;
disk_item.id_len = g_ntohs (disk_item.id_len);
disk_item.nickname_len = g_ntohs (disk_item.nickname_len);
disk_item.full_name_len = g_ntohs (disk_item.full_name_len);
disk_item.given_name_len = g_ntohs (disk_item.given_name_len);
disk_item.surname_len = g_ntohs (disk_item.surname_len);
disk_item.file_as_len = g_ntohs (disk_item.file_as_len);
disk_item.email_1_len = g_ntohs (disk_item.email_1_len);
disk_item.email_2_len = g_ntohs (disk_item.email_2_len);
disk_item.email_3_len = g_ntohs (disk_item.email_3_len);
disk_item.email_4_len = g_ntohs (disk_item.email_4_len);
item = g_new0 (EBookBackendSummaryItem, 1);
item->wants_html = disk_item.wants_html;
item->wants_html_set = disk_item.wants_html_set;
item->list = disk_item.list;
item->list_show_addresses = disk_item.list_show_addresses;
if (disk_item.id_len) {
buf = read_string (fp, disk_item.id_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->id = buf;
}
if (disk_item.nickname_len) {
buf = read_string (fp, disk_item.nickname_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->nickname = buf;
}
if (disk_item.full_name_len) {
buf = read_string (fp, disk_item.full_name_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->full_name = buf;
}
if (disk_item.given_name_len) {
buf = read_string (fp, disk_item.given_name_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->given_name = buf;
}
if (disk_item.surname_len) {
buf = read_string (fp, disk_item.surname_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->surname = buf;
}
if (disk_item.file_as_len) {
buf = read_string (fp, disk_item.file_as_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->file_as = buf;
}
if (disk_item.email_1_len) {
buf = read_string (fp, disk_item.email_1_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->email_1 = buf;
}
if (disk_item.email_2_len) {
buf = read_string (fp, disk_item.email_2_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->email_2 = buf;
}
if (disk_item.email_3_len) {
buf = read_string (fp, disk_item.email_3_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->email_3 = buf;
}
if (disk_item.email_4_len) {
buf = read_string (fp, disk_item.email_4_len);
if (!buf) {
free_summary_item (item);
return FALSE;
}
item->email_4 = buf;
}
/* the only field that has to be there is the id */
if (!item->id) {
free_summary_item (item);
return FALSE;
}
}
else {
/* unhandled file version */
return FALSE;
}
*new_item = item;
return TRUE;
}
/* opens the file and loads the header */
static gboolean
e_book_backend_summary_open (EBookBackendSummary *summary)
{
FILE *fp;
EBookBackendSummaryHeader header;
struct stat sb;
if (summary->priv->fp)
return TRUE;
/* Try opening the summary file. */
fp = g_fopen (summary->priv->summary_path, "rb");
if (!fp) {
/* if there's no summary present, look for the .new
* file and rename it if it's there, and attempt to
* load that */
gchar *new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
if (g_rename (new_filename, summary->priv->summary_path) == -1 &&
errno != ENOENT) {
g_warning (
"%s: Failed to rename '%s' to '%s': %s", G_STRFUNC,
new_filename, summary->priv->summary_path, g_strerror (errno));
} else {
fp = g_fopen (summary->priv->summary_path, "rb");
}
g_free (new_filename);
}
if (!fp) {
g_warning ("failed to open summary file");
return FALSE;
}
if (fstat (fileno (fp), &sb) == -1) {
g_warning ("failed to get summary file size");
fclose (fp);
return FALSE;
}
if (!e_book_backend_summary_check_magic (summary, fp)) {
g_warning ("file is not a valid summary file");
fclose (fp);
return FALSE;
}
if (!e_book_backend_summary_load_header (summary, fp, &header)) {
g_warning ("failed to read summary header");
fclose (fp);
return FALSE;
}
summary->priv->num_items = header.num_items;
summary->priv->file_version = header.file_version;
summary->priv->mtime = sb.st_mtime;
summary->priv->fp = fp;
return TRUE;
}
/**
* e_book_backend_summary_load:
* @summary: an #EBookBackendSummary
*
* Attempts to load @summary from disk. The load is successful if
* the file was located, it was in the correct format, and it was
* not out of date.
*
* Returns: %TRUE if the load succeeded, %FALSE if it failed.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gboolean
e_book_backend_summary_load (EBookBackendSummary *summary)
{
EBookBackendSummaryItem *new_item;
gint i;
g_return_val_if_fail (summary != NULL, FALSE);
clear_items (summary);
if (!e_book_backend_summary_open (summary))
return FALSE;
for (i = 0; i < summary->priv->num_items; i++) {
if (!e_book_backend_summary_load_item (summary, &new_item)) {
g_warning ("error while reading summary item");
clear_items (summary);
fclose (summary->priv->fp);
summary->priv->fp = NULL;
summary->priv->dirty = FALSE;
return FALSE;
}
g_ptr_array_add (summary->priv->items, new_item);
g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);
}
if (summary->priv->upgraded) {
e_book_backend_summary_save (summary);
}
summary->priv->dirty = FALSE;
return TRUE;
}
static gboolean
e_book_backend_summary_save_magic (FILE *fp)
{
gint rv;
rv = fwrite (PAS_SUMMARY_MAGIC, sizeof (gchar), PAS_SUMMARY_MAGIC_LEN, fp);
if (rv != PAS_SUMMARY_MAGIC_LEN)
return FALSE;
return TRUE;
}
static gboolean
e_book_backend_summary_save_header (EBookBackendSummary *summary,
FILE *fp)
{
EBookBackendSummaryHeader header;
gint rv;
header.file_version = g_htonl (PAS_SUMMARY_FILE_VERSION);
header.num_items = g_htonl (summary->priv->items->len);
header.summary_mtime = g_htonl (time (NULL));
rv = fwrite (&header, sizeof (header), 1, fp);
if (rv != 1)
return FALSE;
return TRUE;
}
static gboolean
save_string (const gchar *str,
FILE *fp)
{
size_t rv, len;
if (!str || !*str)
return TRUE;
len = strlen (str);
rv = fwrite (str, sizeof (gchar), len, fp);
return (rv == len);
}
static gboolean
e_book_backend_summary_save_item (EBookBackendSummary *summary,
FILE *fp,
EBookBackendSummaryItem *item)
{
EBookBackendSummaryDiskItem disk_item;
gint len;
gint rv;
len = item->id ? strlen (item->id) : 0;
disk_item.id_len = g_htons (len);
len = item->nickname ? strlen (item->nickname) : 0;
disk_item.nickname_len = g_htons (len);
len = item->given_name ? strlen (item->given_name) : 0;
disk_item.given_name_len = g_htons (len);
len = item->full_name ? strlen (item->full_name) : 0;
disk_item.full_name_len = g_htons (len);
len = item->surname ? strlen (item->surname) : 0;
disk_item.surname_len = g_htons (len);
len = item->file_as ? strlen (item->file_as) : 0;
disk_item.file_as_len = g_htons (len);
len = item->email_1 ? strlen (item->email_1) : 0;
disk_item.email_1_len = g_htons (len);
len = item->email_2 ? strlen (item->email_2) : 0;
disk_item.email_2_len = g_htons (len);
len = item->email_3 ? strlen (item->email_3) : 0;
disk_item.email_3_len = g_htons (len);
len = item->email_4 ? strlen (item->email_4) : 0;
disk_item.email_4_len = g_htons (len);
disk_item.wants_html = item->wants_html;
disk_item.wants_html_set = item->wants_html_set;
disk_item.list = item->list;
disk_item.list_show_addresses = item->list_show_addresses;
rv = fwrite (&disk_item, sizeof (disk_item), 1, fp);
if (rv != 1)
return FALSE;
if (!save_string (item->id, fp))
return FALSE;
if (!save_string (item->nickname, fp))
return FALSE;
if (!save_string (item->full_name, fp))
return FALSE;
if (!save_string (item->given_name, fp))
return FALSE;
if (!save_string (item->surname, fp))
return FALSE;
if (!save_string (item->file_as, fp))
return FALSE;
if (!save_string (item->email_1, fp))
return FALSE;
if (!save_string (item->email_2, fp))
return FALSE;
if (!save_string (item->email_3, fp))
return FALSE;
if (!save_string (item->email_4, fp))
return FALSE;
return TRUE;
}
/**
* e_book_backend_summary_save:
* @summary: an #EBookBackendSummary
*
* Attempts to save @summary to disk.
*
* Returns: %TRUE if the save succeeded, %FALSE otherwise.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gboolean
e_book_backend_summary_save (EBookBackendSummary *summary)
{
struct stat sb;
FILE *fp = NULL;
gchar *new_filename = NULL;
gint i;
g_return_val_if_fail (summary != NULL, FALSE);
if (!summary->priv->dirty)
return TRUE;
new_filename = g_strconcat (summary->priv->summary_path, ".new", NULL);
fp = g_fopen (new_filename, "wb");
if (!fp) {
g_warning ("could not create new summary file");
goto lose;
}
if (!e_book_backend_summary_save_magic (fp)) {
g_warning ("could not write magic to new summary file");
goto lose;
}
if (!e_book_backend_summary_save_header (summary, fp)) {
g_warning ("could not write header to new summary file");
goto lose;
}
for (i = 0; i < summary->priv->items->len; i++) {
EBookBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
if (!e_book_backend_summary_save_item (summary, fp, item)) {
g_warning ("failed to write an item to new summary file, errno = %d", errno);
goto lose;
}
}
fclose (fp);
/* if we have a queued flush, clear it (since we just flushed) */
if (summary->priv->flush_timeout) {
g_source_remove (summary->priv->flush_timeout);
summary->priv->flush_timeout = 0;
}
/* unlink the old summary and rename the new one */
g_unlink (summary->priv->summary_path);
if (g_rename (new_filename, summary->priv->summary_path) == -1) {
g_warning (
"%s: Failed to rename '%s' to '%s': %s", G_STRFUNC,
new_filename, summary->priv->summary_path, g_strerror (errno));
}
g_free (new_filename);
/* lastly, update the in memory mtime to that of the file */
if (g_stat (summary->priv->summary_path, &sb) == -1) {
g_warning ("error stat'ing saved summary");
}
else {
summary->priv->mtime = sb.st_mtime;
}
summary->priv->dirty = FALSE;
return TRUE;
lose:
if (fp)
fclose (fp);
g_unlink (new_filename);
g_free (new_filename);
return FALSE;
}
/**
* e_book_backend_summary_add_contact:
* @summary: an #EBookBackendSummary
* @contact: an #EContact to add
*
* Adds a summary of @contact to @summary. Does not check if
* the contact already has a summary.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
void
e_book_backend_summary_add_contact (EBookBackendSummary *summary,
EContact *contact)
{
EBookBackendSummaryItem *new_item;
gchar *id = NULL;
g_return_if_fail (summary != NULL);
/* ID normally should not be NULL for a contact. */
/* Added this check as groupwise server sometimes returns
* contacts with NULL id
*/
id = e_contact_get (contact, E_CONTACT_UID);
if (!id) {
g_warning ("found a contact with NULL uid");
return;
}
/* Ensure the duplicate contacts are not added */
if (e_book_backend_summary_check_contact (summary, id))
e_book_backend_summary_remove_contact (summary, id);
new_item = g_new0 (EBookBackendSummaryItem, 1);
new_item->id = id;
new_item->nickname = e_contact_get (contact, E_CONTACT_NICKNAME);
new_item->full_name = e_contact_get (contact, E_CONTACT_FULL_NAME);
new_item->given_name = e_contact_get (contact, E_CONTACT_GIVEN_NAME);
new_item->surname = e_contact_get (contact, E_CONTACT_FAMILY_NAME);
new_item->file_as = e_contact_get (contact, E_CONTACT_FILE_AS);
new_item->email_1 = e_contact_get (contact, E_CONTACT_EMAIL_1);
new_item->email_2 = e_contact_get (contact, E_CONTACT_EMAIL_2);
new_item->email_3 = e_contact_get (contact, E_CONTACT_EMAIL_3);
new_item->email_4 = e_contact_get (contact, E_CONTACT_EMAIL_4);
new_item->list = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST));
new_item->list_show_addresses = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_LIST_SHOW_ADDRESSES));
new_item->wants_html = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_WANTS_HTML));
g_ptr_array_add (summary->priv->items, new_item);
g_hash_table_insert (summary->priv->id_to_item, new_item->id, new_item);
#ifdef SUMMARY_STATS
summary->priv->size += sizeof (EBookBackendSummaryItem);
summary->priv->size += new_item->id ? strlen (new_item->id) : 0;
summary->priv->size += new_item->nickname ? strlen (new_item->nickname) : 0;
summary->priv->size += new_item->full_name ? strlen (new_item->full_name) : 0;
summary->priv->size += new_item->given_name ? strlen (new_item->given_name) : 0;
summary->priv->size += new_item->surname ? strlen (new_item->surname) : 0;
summary->priv->size += new_item->file_as ? strlen (new_item->file_as) : 0;
summary->priv->size += new_item->email_1 ? strlen (new_item->email_1) : 0;
summary->priv->size += new_item->email_2 ? strlen (new_item->email_2) : 0;
summary->priv->size += new_item->email_3 ? strlen (new_item->email_3) : 0;
summary->priv->size += new_item->email_4 ? strlen (new_item->email_4) : 0;
#endif
e_book_backend_summary_touch (summary);
}
/**
* e_book_backend_summary_remove_contact:
* @summary: an #EBookBackendSummary
* @id: a unique contact ID string
*
* Removes the summary of the contact identified by @id from @summary.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
void
e_book_backend_summary_remove_contact (EBookBackendSummary *summary,
const gchar *id)
{
EBookBackendSummaryItem *item;
g_return_if_fail (summary != NULL);
item = g_hash_table_lookup (summary->priv->id_to_item, id);
if (item) {
g_ptr_array_remove (summary->priv->items, item);
g_hash_table_remove (summary->priv->id_to_item, id);
free_summary_item (item);
e_book_backend_summary_touch (summary);
return;
}
g_warning ("e_book_backend_summary_remove_contact: unable to locate id `%s'", id);
}
/**
* e_book_backend_summary_check_contact:
* @summary: an #EBookBackendSummary
* @id: a unique contact ID string
*
* Checks if a summary of the contact identified by @id
* exists in @summary.
*
* Returns: %TRUE if the summary exists, %FALSE otherwise.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gboolean
e_book_backend_summary_check_contact (EBookBackendSummary *summary,
const gchar *id)
{
g_return_val_if_fail (summary != NULL, FALSE);
return g_hash_table_lookup (summary->priv->id_to_item, id) != NULL;
}
static gboolean
summary_flush_func (gpointer data)
{
EBookBackendSummary *summary = E_BOOK_BACKEND_SUMMARY (data);
if (!summary->priv->dirty) {
summary->priv->flush_timeout = 0;
return FALSE;
}
if (!e_book_backend_summary_save (summary)) {
/* this isn't fatal, as we can just either 1) flush
* out with the next change, or 2) regen the summary
* when we next load the uri */
g_warning ("failed to flush summary file to disk");
return TRUE; /* try again after the next timeout */
}
g_message ("Flushed summary to disk");
/* we only want this to execute once, so return FALSE and set
* summary->flush_timeout to 0 */
summary->priv->flush_timeout = 0;
return FALSE;
}
/**
* e_book_backend_summary_touch:
* @summary: an #EBookBackendSummary
*
* Indicates that @summary has changed and should be flushed to disk.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
void
e_book_backend_summary_touch (EBookBackendSummary *summary)
{
g_return_if_fail (summary != NULL);
summary->priv->dirty = TRUE;
if (!summary->priv->flush_timeout
&& summary->priv->flush_timeout_millis) {
summary->priv->flush_timeout = e_named_timeout_add (
summary->priv->flush_timeout_millis,
summary_flush_func, summary);
}
}
/**
* e_book_backend_summary_is_up_to_date:
* @summary: an #EBookBackendSummary
* @t: the time to compare with
*
* Checks if @summary is more recent than @t.
*
* Returns: %TRUE if the summary is up to date, %FALSE otherwise.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gboolean
e_book_backend_summary_is_up_to_date (EBookBackendSummary *summary,
time_t t)
{
g_return_val_if_fail (summary != NULL, FALSE);
if (!e_book_backend_summary_open (summary))
return FALSE;
else
return summary->priv->mtime >= t;
}
/* we only want to do summary queries if the query is over the set fields in the summary */
static ESExpResult *
func_check (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
ESExpResult *r;
gint truth = FALSE;
gboolean *pretval = data;
if (argc == 2
&& argv[0]->type == ESEXP_RES_STRING
&& argv[1]->type == ESEXP_RES_STRING) {
gchar *query_name = argv[0]->value.string;
if (!strcmp (query_name, "nickname") ||
!strcmp (query_name, "full_name") ||
!strcmp (query_name, "file_as") ||
!strcmp (query_name, "email")) {
truth = TRUE;
}
}
r = e_sexp_result_new (f, ESEXP_RES_BOOL);
r->value.boolean = truth;
if (pretval)
*pretval = (*pretval) && truth;
return r;
}
/* 'builtin' functions */
static const struct {
const gchar *name;
ESExpFunc *func;
gint type; /* set to 1 if a function can perform shortcut evaluation, or
doesn't execute everything, 0 otherwise */
} check_symbols[] = {
{ "contains", func_check, 0 },
{ "is", func_check, 0 },
{ "beginswith", func_check, 0 },
{ "endswith", func_check, 0 },
{ "exists", func_check, 0 },
{ "exists_vcard", func_check, 0 }
};
/**
* e_book_backend_summary_is_summary_query:
* @summary: an #EBookBackendSummary
* @query: an s-expression to check
*
* Checks if @query can be satisfied by searching only the fields
* stored by @summary.
*
* Returns: %TRUE if the query can be satisfied, %FALSE otherwise.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gboolean
e_book_backend_summary_is_summary_query (EBookBackendSummary *summary,
const gchar *query)
{
ESExp *sexp;
ESExpResult *r;
gboolean retval = TRUE;
gint i;
gint esexp_error;
g_return_val_if_fail (summary != NULL, FALSE);
sexp = e_sexp_new ();
for (i = 0; i < G_N_ELEMENTS (check_symbols); i++) {
if (check_symbols[i].type == 1) {
e_sexp_add_ifunction (sexp, 0, check_symbols[i].name,
(ESExpIFunc *) check_symbols[i].func, &retval);
} else {
e_sexp_add_function (
sexp, 0, check_symbols[i].name,
check_symbols[i].func, &retval);
}
}
e_sexp_input_text (sexp, query, strlen (query));
esexp_error = e_sexp_parse (sexp);
if (esexp_error == -1) {
g_object_unref (sexp);
return FALSE;
}
r = e_sexp_eval (sexp);
retval = retval && (r && r->type == ESEXP_RES_BOOL && r->value.boolean);
e_sexp_result_free (sexp, r);
g_object_unref (sexp);
return retval;
}
/* the actual query mechanics */
static ESExpResult *
do_compare (EBookBackendSummary *summary,
struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gchar *(*compare)(const gchar *, const gchar *))
{
GPtrArray *result = g_ptr_array_new ();
ESExpResult *r;
gint i;
if (argc == 2
&& argv[0]->type == ESEXP_RES_STRING
&& argv[1]->type == ESEXP_RES_STRING) {
for (i = 0; i < summary->priv->items->len; i++) {
EBookBackendSummaryItem *item = g_ptr_array_index (summary->priv->items, i);
if (!strcmp (argv[0]->value.string, "full_name")) {
gchar *given = item->given_name;
gchar *surname = item->surname;
gchar *full_name = item->full_name;
if ((given && compare (given, argv[1]->value.string))
|| (surname && compare (surname, argv[1]->value.string))
|| (full_name && compare (full_name, argv[1]->value.string)))
g_ptr_array_add (result, item->id);
}
else if (!strcmp (argv[0]->value.string, "email")) {
gchar *email_1 = item->email_1;
gchar *email_2 = item->email_2;
gchar *email_3 = item->email_3;
gchar *email_4 = item->email_4;
if ((email_1 && compare (email_1, argv[1]->value.string))
|| (email_2 && compare (email_2, argv[1]->value.string))
|| (email_3 && compare (email_3, argv[1]->value.string))
|| (email_4 && compare (email_4, argv[1]->value.string)))
g_ptr_array_add (result, item->id);
}
else if (!strcmp (argv[0]->value.string, "file_as")) {
gchar *file_as = item->file_as;
if (file_as && compare (file_as, argv[1]->value.string))
g_ptr_array_add (result, item->id);
}
else if (!strcmp (argv[0]->value.string, "nickname")) {
gchar *nickname = item->nickname;
if (nickname && compare (nickname, argv[1]->value.string))
g_ptr_array_add (result, item->id);
}
}
}
r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
r->value.ptrarray = result;
return r;
}
static gchar *
contains_helper (const gchar *ps1,
const gchar *ps2)
{
gchar *s1 = e_util_utf8_remove_accents (ps1);
gchar *s2 = e_util_utf8_remove_accents (ps2);
gchar *res;
res = (gchar *) e_util_utf8_strstrcase (s1, s2);
g_free (s1);
g_free (s2);
return res;
}
static ESExpResult *
func_contains (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
EBookBackendSummary *summary = data;
return do_compare (summary, f, argc, argv, contains_helper);
}
static gchar *
is_helper (const gchar *ps1,
const gchar *ps2)
{
gchar *s1 = e_util_utf8_remove_accents (ps1);
gchar *s2 = e_util_utf8_remove_accents (ps2);
gchar *res;
if (!e_util_utf8_strcasecmp (s1, s2))
res = (gchar *) ps1;
else
res = NULL;
g_free (s1);
g_free (s2);
return res;
}
static ESExpResult *
func_is (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
EBookBackendSummary *summary = data;
return do_compare (summary, f, argc, argv, is_helper);
}
static gchar *
endswith_helper (const gchar *ps1,
const gchar *ps2)
{
gchar *s1 = e_util_utf8_remove_accents (ps1);
gchar *s2 = e_util_utf8_remove_accents (ps2);
gchar *res;
glong s1len = g_utf8_strlen (s1, -1);
glong s2len = g_utf8_strlen (s2, -1);
if (s1len < s2len)
res = NULL;
else
res = (gchar *) e_util_utf8_strstrcase (g_utf8_offset_to_pointer (s1, s1len - s2len), s2);
g_free (s1);
g_free (s2);
return res;
}
static ESExpResult *
func_endswith (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
EBookBackendSummary *summary = data;
return do_compare (summary, f, argc, argv, endswith_helper);
}
static gchar *
beginswith_helper (const gchar *ps1,
const gchar *ps2)
{
gchar *p, *res;
gchar *s1 = e_util_utf8_remove_accents (ps1);
gchar *s2 = e_util_utf8_remove_accents (ps2);
if ((p = (gchar *) e_util_utf8_strstrcase (s1, s2))
&& (p == s1))
res = (gchar *) ps1;
else
res = NULL;
g_free (s1);
g_free (s2);
return res;
}
static ESExpResult *
func_beginswith (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
EBookBackendSummary *summary = data;
return do_compare (summary, f, argc, argv, beginswith_helper);
}
/* 'builtin' functions */
static const struct {
const gchar *name;
ESExpFunc *func;
gint type; /* set to 1 if a function can perform shortcut evaluation, or
doesn't execute everything, 0 otherwise */
} symbols[] = {
{ "contains", func_contains, 0 },
{ "is", func_is, 0 },
{ "beginswith", func_beginswith, 0 },
{ "endswith", func_endswith, 0 },
};
/**
* e_book_backend_summary_search:
* @summary: an #EBookBackendSummary
* @query: an s-expression
*
* Searches @summary for contacts matching @query.
*
* Returns: (element-type utf8) (transfer container): A #GPtrArray of pointers to contact ID strings.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
GPtrArray *
e_book_backend_summary_search (EBookBackendSummary *summary,
const gchar *query)
{
ESExp *sexp;
ESExpResult *r;
GPtrArray *retval;
gint i;
gint esexp_error;
g_return_val_if_fail (summary != NULL, NULL);
sexp = e_sexp_new ();
for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
if (symbols[i].type == 1) {
e_sexp_add_ifunction (sexp, 0, symbols[i].name,
(ESExpIFunc *) symbols[i].func, summary);
} else {
e_sexp_add_function (
sexp, 0, symbols[i].name,
symbols[i].func, summary);
}
}
e_sexp_input_text (sexp, query, strlen (query));
esexp_error = e_sexp_parse (sexp);
if (esexp_error == -1) {
g_object_unref (sexp);
return NULL;
}
retval = g_ptr_array_new ();
r = e_sexp_eval (sexp);
if (r && r->type == ESEXP_RES_ARRAY_PTR && r->value.ptrarray) {
GPtrArray *ptrarray = r->value.ptrarray;
gint i;
for (i = 0; i < ptrarray->len; i++)
g_ptr_array_add (retval, g_ptr_array_index (ptrarray, i));
}
e_sexp_result_free (sexp, r);
g_object_unref (sexp);
return retval;
}
/**
* e_book_backend_summary_get_summary_vcard:
* @summary: an #EBookBackendSummary
* @id: a unique contact ID
*
* Constructs and returns a VCard from the contact summary specified by @id.
*
* Returns: (nullable): A new VCard, or %NULL if the contact summary
* didn't exist.
*
* Deprecated: 3.12: Use #EBookSqlite instead
**/
gchar *
e_book_backend_summary_get_summary_vcard (EBookBackendSummary *summary,
const gchar *id)
{
EBookBackendSummaryItem *item;
g_return_val_if_fail (summary != NULL, NULL);
item = g_hash_table_lookup (summary->priv->id_to_item, id);
if (item) {
EContact *contact = e_contact_new ();
gchar *vcard;
e_contact_set (contact, E_CONTACT_UID, item->id);
e_contact_set (contact, E_CONTACT_FILE_AS, item->file_as);
e_contact_set (contact, E_CONTACT_GIVEN_NAME, item->given_name);
e_contact_set (contact, E_CONTACT_FAMILY_NAME, item->surname);
e_contact_set (contact, E_CONTACT_NICKNAME, item->nickname);
e_contact_set (contact, E_CONTACT_FULL_NAME, item->full_name);
e_contact_set (contact, E_CONTACT_EMAIL_1, item->email_1);
e_contact_set (contact, E_CONTACT_EMAIL_2, item->email_2);
e_contact_set (contact, E_CONTACT_EMAIL_3, item->email_3);
e_contact_set (contact, E_CONTACT_EMAIL_4, item->email_4);
e_contact_set (contact, E_CONTACT_IS_LIST, GINT_TO_POINTER (item->list));
e_contact_set (contact, E_CONTACT_LIST_SHOW_ADDRESSES, GINT_TO_POINTER (item->list_show_addresses));
e_contact_set (contact, E_CONTACT_WANTS_HTML, GINT_TO_POINTER (item->wants_html));
vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
g_object_unref (contact);
return vcard;
}
else {
g_warning ("in unable to locate card `%s' in summary", id);
return NULL;
}
}