blob: c2e8649d135a30e3f6556bbd62bc690d8325ee13 [file] [log] [blame]
/*
*
* OBEX Server
*
* Copyright (C) 2009-2010 Intel Corporation
* Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.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 St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <bluetooth/bluetooth.h>
#include <libebook/e-book.h>
#include "log.h"
#include "obex.h"
#include "service.h"
#include "phonebook.h"
#include "glib-helper.h"
#define QUERY_FN "(contains \"family_name\" \"%s\")"
#define QUERY_NAME "(contains \"given_name\" \"%s\")"
#define QUERY_PHONE "(contains \"phone\" \"%s\")"
struct query_context {
const struct apparam_field *params;
phonebook_cb contacts_cb;
phonebook_entry_cb entry_cb;
phonebook_cache_ready_cb ready_cb;
EBookQuery *query;
unsigned int count;
GString *buf;
char *id;
unsigned queued_calls;
void *user_data;
GSList *ebooks;
gboolean canceled;
};
static char *attribute_mask[] = {
/* 0 */ "VERSION",
"FN",
"N",
"PHOTO",
"BDAY",
"ADR",
"LABEL",
"TEL",
/* 8 */ "EMAIL",
"MAILER",
"TZ",
"GEO",
"TITLE",
"ROLE",
"LOGO",
"AGENT",
/* 16 */ "ORG",
"NOTE",
"REV",
"SOUND",
"URL",
"UID",
"KEY",
"NICKNAME",
/* 24 */ "CATEGORIES",
"PROID",
"CLASS",
"SORT-STRING",
/* 28 */ "X-IRMC-CALL-DATETIME",
NULL
};
static void close_ebooks(GSList *ebooks)
{
g_slist_free_full(ebooks, g_object_unref);
}
static void free_query_context(struct query_context *data)
{
g_free(data->id);
if (data->buf != NULL)
g_string_free(data->buf, TRUE);
if (data->query != NULL)
e_book_query_unref(data->query);
close_ebooks(data->ebooks);
g_free(data);
}
static char *evcard_to_string(EVCard *evcard, unsigned int format,
uint64_t filter)
{
EVCard *evcard2;
GList *l;
char *vcard;
if (!filter)
return e_vcard_to_string(evcard, EVC_FORMAT_VCARD_30);
/* XXX There is no support for VCARD 2.1 at this time */
/*
* Mandatory attributes for vCard 2.1 are VERSION ,N and TEL.
* Mandatory attributes for vCard 3.0 are VERSION, N, FN and TEL
*/
filter = format == EVC_FORMAT_VCARD_30 ? filter | 0x87: filter | 0x85;
l = e_vcard_get_attributes(evcard);
evcard2 = e_vcard_new();
for (; l; l = g_list_next(l)) {
EVCardAttribute *attrib = l->data;
const char *name;
int i;
if (!attrib)
continue;
name = e_vcard_attribute_get_name(attrib);
for (i = 0; attribute_mask[i] != NULL; i++) {
if (!(filter & (1 << i)))
continue;
if (g_strcmp0(name, attribute_mask[i]) != 0)
continue;
e_vcard_add_attribute(evcard2,
e_vcard_attribute_copy(attrib));
}
}
vcard = e_vcard_to_string(evcard2, format);
g_object_unref(evcard2);
return vcard;
}
static void ebookpull_cb(EBook *book, const GError *gerr, GList *contacts,
void *user_data)
{
struct query_context *data = user_data;
GList *l;
unsigned int count, maxcount;
data->queued_calls--;
if (data->canceled)
goto canceled;
if (gerr != NULL) {
error("E-Book query failed: %s", gerr->message);
goto done;
}
DBG("");
/*
* When MaxListCount is zero, PCE wants to know the number of used
* indexes in the phonebook of interest. All other parameters that
* may be present in the request shall be ignored.
*/
maxcount = data->params->maxlistcount;
if (maxcount == 0) {
data->count += g_list_length(contacts);
goto done;
}
l = g_list_nth(contacts, data->params->liststartoffset);
for (count = 0; l && count + data->count < maxcount; l = g_list_next(l),
count++) {
EContact *contact = E_CONTACT(l->data);
EVCard *evcard = E_VCARD(contact);
char *vcard;
vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
data->params->filter);
data->buf = g_string_append(data->buf, vcard);
data->buf = g_string_append(data->buf, "\r\n");
g_free(vcard);
}
DBG("collected %d vcards", count);
data->count += count;
g_list_free_full(contacts, g_object_unref);
done:
if (data->queued_calls == 0) {
GString *buf = data->buf;
data->buf = NULL;
data->contacts_cb(buf->str, buf->len, data->count,
0, TRUE, data->user_data);
g_string_free(buf, TRUE);
}
return;
canceled:
if (data->queued_calls == 0)
free_query_context(data);
}
static void ebook_entry_cb(EBook *book, const GError *gerr,
EContact *contact, void *user_data)
{
struct query_context *data = user_data;
EVCard *evcard;
char *vcard;
size_t len;
data->queued_calls--;
if (data->canceled)
goto done;
if (gerr != NULL) {
error("E-Book query failed: %s", gerr->message);
goto done;
}
DBG("");
evcard = E_VCARD(contact);
vcard = evcard_to_string(evcard, EVC_FORMAT_VCARD_30,
data->params->filter);
len = vcard ? strlen(vcard) : 0;
data->count++;
data->contacts_cb(vcard, len, 1, 0, TRUE, data->user_data);
g_free(vcard);
g_object_unref(contact);
return;
done:
if (data->queued_calls == 0) {
if (data->count == 0)
data->contacts_cb(NULL, 0, 1, 0, TRUE,
data->user_data);
else if (data->canceled)
free_query_context(data);
}
}
static char *evcard_name_attribute_to_string(EVCard *evcard)
{
EVCardAttribute *attrib;
GList *l;
GString *name = NULL;
attrib = e_vcard_get_attribute(evcard, EVC_N);
if (!attrib)
return NULL;
for (l = e_vcard_attribute_get_values(attrib); l; l = l->next) {
const char *value = l->data;
if (!strlen(value))
continue;
if (!name)
name = g_string_new(value);
else {
name = g_string_append(name, ";");
name = g_string_append(name, l->data);
}
}
if (!name)
return NULL;
return g_string_free(name, FALSE);
}
static void cache_cb(EBook *book, const GError *gerr, GList *contacts,
void *user_data)
{
struct query_context *data = user_data;
GList *l;
data->queued_calls--;
if (data->canceled)
goto canceled;
if (gerr != NULL) {
error("E-Book operation failed: %s", gerr->message);
goto done;
}
DBG("");
for (l = contacts; l; l = g_list_next(l)) {
EContact *contact = E_CONTACT(l->data);
EVCard *evcard = E_VCARD(contact);
EVCardAttribute *attrib;
char *uid, *tel, *name;
name = evcard_name_attribute_to_string(evcard);
if (!name)
continue;
attrib = e_vcard_get_attribute(evcard, EVC_UID);
if (!attrib)
continue;
uid = e_vcard_attribute_get_value(attrib);
if (!uid)
continue;
attrib = e_vcard_get_attribute(evcard, EVC_TEL);
if (attrib)
tel = e_vcard_attribute_get_value(attrib);
else
tel = g_strdup("");
data->entry_cb(uid, PHONEBOOK_INVALID_HANDLE, name, NULL,
tel, data->user_data);
g_free(name);
g_free(uid);
g_free(tel);
}
g_list_free_full(contacts, g_object_unref);
done:
if (data->queued_calls == 0)
data->ready_cb(data->user_data);
return;
canceled:
if (data->queued_calls == 0)
free_query_context(data);
}
static GSList *traverse_sources(GSList *ebooks, GSList *sources,
char **default_src) {
GError *gerr = NULL;
for (; sources != NULL; sources = g_slist_next(sources)) {
char *uri;
ESource *source = E_SOURCE(sources->data);
EBook *ebook = e_book_new(source, &gerr);
if (ebook == NULL) {
error("Can't create user's address book: %s",
gerr->message);
g_clear_error(&gerr);
continue;
}
uri = e_source_get_uri(source);
if (g_strcmp0(*default_src, uri) == 0) {
g_free(uri);
continue;
}
g_free(uri);
if (e_book_open(ebook, FALSE, &gerr) == FALSE) {
error("Can't open e-book address book: %s",
gerr->message);
g_object_unref(ebook);
g_clear_error(&gerr);
continue;
}
if (*default_src == NULL)
*default_src = e_source_get_uri(source);
DBG("%s address book opened", e_source_peek_name(source));
ebooks = g_slist_append(ebooks, ebook);
}
return ebooks;
}
int phonebook_init(void)
{
g_type_init();
return 0;
}
static GSList *open_ebooks(void)
{
GError *gerr = NULL;
ESourceList *src_list;
GSList *list;
gchar *default_src = NULL;
GSList *ebooks = NULL;
if (e_book_get_addressbooks(&src_list, &gerr) == FALSE) {
error("Can't list user's address books: %s", gerr->message);
g_error_free(gerr);
return NULL;
}
list = e_source_list_peek_groups(src_list);
while (list != NULL) {
ESourceGroup *group = E_SOURCE_GROUP(list->data);
GSList *sources = e_source_group_peek_sources(group);
ebooks = traverse_sources(ebooks, sources, &default_src);
list = list->next;
}
g_free(default_src);
g_object_unref(src_list);
return ebooks;
}
void phonebook_exit(void)
{
}
char *phonebook_set_folder(const char *current_folder,
const char *new_folder, uint8_t flags, int *err)
{
gboolean root, child;
char *fullname = NULL, *tmp1, *tmp2, *base;
int ret = 0, len;
root = (g_strcmp0("/", current_folder) == 0);
child = (new_folder && strlen(new_folder) != 0);
/* Evolution back-end will support telecom/pb folder only */
switch (flags) {
case 0x02:
/* Go back to root */
if (!child) {
fullname = g_strdup("/");
goto done;
}
/* Go down 1 level */
fullname = g_build_filename(current_folder, new_folder, NULL);
if (strcmp("/telecom", fullname) != 0 &&
strcmp("/telecom/pb", fullname) != 0) {
g_free(fullname);
fullname = NULL;
ret = -ENOENT;
}
break;
case 0x03:
/* Go up 1 level */
if (root) {
/* Already root */
ret = -EBADR;
goto done;
}
/*
* Removing one level of the current folder. Current folder
* contains AT LEAST one level since it is not at root folder.
* Use glib utility functions to handle invalid chars in the
* folder path properly.
*/
tmp1 = g_path_get_basename(current_folder);
tmp2 = g_strrstr(current_folder, tmp1);
len = tmp2 - (current_folder + 1);
g_free(tmp1);
if (len == 0)
base = g_strdup("/");
else
base = g_strndup(current_folder, len);
/* Return one level only */
if (!child) {
fullname = base;
goto done;
}
fullname = g_build_filename(base, new_folder, NULL);
if (strcmp(fullname, "/telecom") != 0 &&
strcmp(fullname, "/telecom/pb") != 0) {
g_free(fullname);
fullname = NULL;
ret = -ENOENT;
}
g_free(base);
break;
default:
ret = -EBADR;
break;
}
done:
if (err)
*err = ret;
return fullname;
}
void phonebook_req_finalize(void *request)
{
struct query_context *data = request;
if (data->queued_calls == 0)
free_query_context(data);
else
data->canceled = TRUE;
}
void *phonebook_pull(const char *name, const struct apparam_field *params,
phonebook_cb cb, void *user_data, int *err)
{
struct query_context *data;
if (g_strcmp0("/telecom/pb.vcf", name) != 0) {
if (err)
*err = -ENOENT;
return NULL;
}
data = g_new0(struct query_context, 1);
data->contacts_cb = cb;
data->params = params;
data->user_data = user_data;
data->buf = g_string_new("");
data->query = e_book_query_any_field_contains("");
data->ebooks = open_ebooks();
if (err)
*err = data->ebooks == NULL ? -EIO : 0;
return data;
}
int phonebook_pull_read(void *request)
{
struct query_context *data = request;
GSList *l;
if (!data)
return -ENOENT;
for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
EBook *ebook = l->data;
if (e_book_is_opened(ebook) == FALSE)
continue;
if (e_book_get_contacts_async(ebook, data->query,
ebookpull_cb, data) == TRUE)
data->queued_calls++;
}
if (data->queued_calls == 0)
return -ENOENT;
return 0;
}
void *phonebook_get_entry(const char *folder, const char *id,
const struct apparam_field *params,
phonebook_cb cb, void *user_data, int *err)
{
struct query_context *data;
GSList *l;
data = g_new0(struct query_context, 1);
data->contacts_cb = cb;
data->params = params;
data->user_data = user_data;
data->id = g_strdup(id);
data->ebooks = open_ebooks();
for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
EBook *ebook = l->data;
if (e_book_is_opened(ebook) == FALSE)
continue;
if (e_book_get_contact_async(ebook, data->id,
ebook_entry_cb, data) == TRUE)
data->queued_calls++;
}
if (err)
*err = (data->queued_calls == 0 ? -ENOENT : 0);
return data;
}
void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
{
struct query_context *data;
EBookQuery *query;
GSList *l;
EContact *me;
EVCard *evcard;
GError *gerr = NULL;
EBook *eb;
EVCardAttribute *attrib;
char *uid, *tel, *cname;
if (g_strcmp0("/telecom/pb", name) != 0) {
if (err)
*err = -ENOENT;
return NULL;
}
DBG("");
query = e_book_query_any_field_contains("");
data = g_new0(struct query_context, 1);
data->entry_cb = entry_cb;
data->ready_cb = ready_cb;
data->user_data = user_data;
data->query = query;
data->ebooks = open_ebooks();
/* Add 0.vcf */
if (e_book_get_self(&me, &eb, &gerr) == FALSE) {
g_error_free(gerr);
goto next;
}
evcard = E_VCARD(me);
cname = evcard_name_attribute_to_string(evcard);
if (!cname)
cname = g_strdup("");
attrib = e_vcard_get_attribute(evcard, EVC_UID);
uid = e_vcard_attribute_get_value(attrib);
if (!uid)
uid = g_strdup("");
attrib = e_vcard_get_attribute(evcard, EVC_TEL);
if (attrib)
tel = e_vcard_attribute_get_value(attrib);
else
tel = g_strdup("");
data->entry_cb(uid, 0, cname, NULL, tel, data->user_data);
data->count++;
g_free(cname);
g_free(uid);
g_free(tel);
g_object_unref(eb);
next:
for (l = data->ebooks; l != NULL; l = g_slist_next(l)) {
EBook *ebook = l->data;
if (e_book_is_opened(ebook) == FALSE)
continue;
if (e_book_get_contacts_async(ebook, query,
cache_cb, data) == TRUE)
data->queued_calls++;
}
if (err)
*err = (data->queued_calls == 0 ? -ENOENT : 0);
return data;
}