diff --git a/obexd/plugins/phonebook-tracker.c b/obexd/plugins/phonebook-tracker.c
new file mode 100644
index 0000000..9b431d8
--- /dev/null
+++ b/obexd/plugins/phonebook-tracker.c
+/*
+ * Phonebook access through D-Bus vCard and call history service
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ *
+ * 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
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <openobex/obex.h>
+#include <openobex/obex_const.h>
+
+#include "logging.h"
+#include "obex.h"
+#include "service.h"
+#include "mimetype.h"
+#include "phonebook.h"
+#include "dbus.h"
+#include "vcard.h"
+
+#define TRACKER_SERVICE "org.freedesktop.Tracker1"
+#define TRACKER_RESOURCES_PATH "/org/freedesktop/Tracker1/Resources"
+#define TRACKER_RESOURCES_INTERFACE "org.freedesktop.Tracker1.Resources"
+
+#define TRACKER_DEFAULT_CONTACT_ME "<urn:nco:default-contact-me>"
+
+#define CONTACTS_QUERY_ALL \
+ "SELECT ?phone ?family ?given ?additional ?prefix " \
+ "?suffix ?email " \
+ "WHERE { " \
+ "?contact a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { ?contact nco:hasEmailAddress ?email } " \
+ "OPTIONAL { ?contact nco:nameAdditional ?additional } " \
+ "OPTIONAL { ?contact nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { ?contact nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+#define CONTACTS_QUERY_ALL_LIST \
+ "SELECT ?contact ?family ?given ?additional ?prefix " \
+ "?suffix ?phone " \
+ "WHERE { " \
+ "?contact a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { ?contact nco:nameAdditional ?additional } " \
+ "OPTIONAL { ?contact nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { ?contact nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+#define MISSED_CALLS_QUERY \
+ "SELECT ?contact ?family ?given ?additional ?prefix " \
+ "?suffix ?phone ?email " \
+ "WHERE { " \
+ "?call a nmo:Call ; " \
+ "nmo:from ?contact ; " \
+ "nmo:to <nco:default-contact-me> ; " \
+ "nmo:isRead false ." \
+ "?contact a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { ?contact nco:hasEmailAddress ?email } " \
+ "OPTIONAL { ?contact nco:nameAdditional ?additional } " \
+ "OPTIONAL { ?contact nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { ?contact nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+#define INCOMING_CALLS_QUERY \
+ "SELECT ?contact ?family ?given ?additional ?prefix " \
+ "?suffix ?phone ?fullname ?email " \
+ "WHERE { " \
+ "?call a nmo:Call ; " \
+ "nmo:from ?contact ; " \
+ "nmo:to " TRACKER_DEFAULT_CONTACT_ME " . " \
+ "?contact a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { ?contact nco:hasEmailAddress ?email } " \
+ "OPTIONAL { ?contact nco:nameAdditional ?additional } " \
+ "OPTIONAL { ?contact nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { ?contact nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+#define OUTGOING_CALLS_QUERY \
+ "SELECT ?contact ?family ?given ?additional ?prefix " \
+ "?suffix ?phone ?fullname ?email " \
+ "WHERE { " \
+ "?call a nmo:Call ; " \
+ "nmo:to ?contact ; " \
+ "nmo:from " TRACKER_DEFAULT_CONTACT_ME " . " \
+ "?contact a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { ?contact nco:hasEmailAddress ?email } " \
+ "OPTIONAL { ?contact nco:nameAdditional ?additional } " \
+ "OPTIONAL { ?contact nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { ?contact nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+/* FIXME: still not sure about how to implement this */
+#define COMBINED_CALLS_QUERY \
+ "SELECT ?contact " \
+ "WHERE { " \
+ "?call a nmo:Call . " \
+ "}"
+
+#define CONTACTS_QUERY_FROM_URI \
+ "SELECT ?phone ?family ?given ?additional ?prefix " \
+ " ?suffix ?email " \
+ "WHERE { " \
+ "<%s> a nco:PersonContact ; " \
+ "nco:nameFamily ?family ; " \
+ "nco:nameGiven ?given ; " \
+ "nco:hasPhoneNumber ?phone ." \
+ "OPTIONAL { <%s> nco:hasEmailAddress ?email } " \
+ "OPTIONAL { <%s> nco:nameAdditional ?additional } " \
+ "OPTIONAL { <%s> nco:nameHonorificPrefix ?prefix } " \
+ "OPTIONAL { <%s> nco:nameHonorificSuffix ?suffix } " \
+ "}"
+
+typedef void (*reply_list_foreach_t) (char **reply, int num_fields,
+ void *user_data);
+
+struct pending_reply {
+ reply_list_foreach_t callback;
+ void *user_data;
+ int num_fields;
+};
+
+struct phonebook_data {
+ GString *vcards;
+ phonebook_cb cb;
+ void *user_data;
+ int index;
+};
+
+struct cache_data {
+ phonebook_cache_ready_cb ready_cb;
+ phonebook_entry_cb entry_cb;
+ void *user_data;
+ GString *listing;
+ int index;
+};
+
+struct phonebook_index {
+ GArray *phonebook;
+ int index;
+};
+
+static DBusConnection *connection = NULL;
+
+static char **string_array_from_iter(DBusMessageIter iter, int array_len)
+{
+ DBusMessageIter sub;
+ char **result;
+ int i;
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
+ return NULL;
+
+ result = g_new0(char *, array_len);
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ i = 0;
+ while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+ char *arg;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+ goto error;
+
+ dbus_message_iter_get_basic(&sub, &arg);
+
+ result[i] = arg;
+
+ i++;
+ dbus_message_iter_next(&sub);
+ }
+
+ return result;
+
+error:
+ g_free(result);
+
+ return NULL;
+}
+
+static void query_reply(DBusPendingCall *call, void *user_data)
+{
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ struct pending_reply *pending = user_data;
+ DBusMessageIter iter, element;
+ DBusError derr;
+ int err;
+
+ dbus_error_init(&derr);
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("Replied with an error: %s, %s", derr.name,
+ derr.message);
+ dbus_error_free(&derr);
+
+ err = -1;
+ goto done;
+ }
+
+ dbus_message_iter_init(reply, &iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ error("SparqlQuery reply is not an array");
+
+ err = -1;
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&iter, &element);
+
+ err = 0;
+
+ while (dbus_message_iter_get_arg_type(&element) != DBUS_TYPE_INVALID) {
+ char **node;
+
+ if (dbus_message_iter_get_arg_type(&element) !=
+ DBUS_TYPE_ARRAY) {
+ error("element is not an array");
+ goto done;
+ }
+
+ node = string_array_from_iter(element, pending->num_fields);
+ pending->callback(node, pending->num_fields,
+ pending->user_data);
+ g_free(node);
+
+ dbus_message_iter_next(&element);
+ }
+
+done:
+ /* This is the last entry */
+ pending->callback(NULL, err, pending->user_data);
+
+ dbus_message_unref(reply);
+ g_free(pending);
+}
+
+static int query_tracker(const char* query, int num_fields,
+ reply_list_foreach_t callback, void *user_data)
+{
+ struct pending_reply *pending;
+ DBusPendingCall *call;
+ DBusMessage *msg;
+
+ if (connection == NULL)
+ connection = obex_dbus_get_connection();
+
+ msg = dbus_message_new_method_call(TRACKER_SERVICE,
+ TRACKER_RESOURCES_PATH, TRACKER_RESOURCES_INTERFACE,
+ "SparqlQuery");
+
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &query,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, msg, &call,
+ -1) == FALSE) {
+ error("Could not send dbus message");
+ dbus_message_unref(msg);
+ return -EPERM;
+ }
+
+ pending = g_new0(struct pending_reply, 1);
+ pending->callback = callback;
+ pending->user_data = user_data;
+ pending->num_fields = num_fields;
+
+ dbus_pending_call_set_notify(call, query_reply, pending, NULL);
+ dbus_pending_call_unref(call);
+ dbus_message_unref(msg);
+
+ return 0;
+}
+
+static void pull_contacts(char **reply, int num_fields, void *user_data)
+{
+ struct phonebook_data *data = user_data;
+ GString *vcards = data->vcards;
+ char *formatted;
+
+ if (reply == NULL)
+ goto done;
+
+ formatted = g_strdup_printf("%s;%s;%s;%s;%s", reply[1], reply[2],
+ reply[3], reply[4], reply[5]);
+
+ phonebook_add_entry(vcards, reply[0], TEL_TYPE_HOME, formatted,
+ reply[6]);
+
+ g_free(formatted);
+
+ data->index++;
+
+ return;
+
+done:
+ if (num_fields == 0)
+ data->cb(vcards->str, vcards->len, data->index, 0, data->user_data);
+
+ g_string_free(vcards, TRUE);
+}
+
+static void add_to_cache(char **reply, int num_fields, void *user_data)
+{
+ struct cache_data *cache = user_data;
+ char *formatted;
+
+ if (reply == NULL)
+ goto done;
+
+ formatted = g_strdup_printf("%s;%s;%s;%s;%s", reply[1], reply[2],
+ reply[3], reply[4], reply[5]);
+
+ cache->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted, "",
+ reply[6], cache->user_data);
+
+ g_free(formatted);
+
+ return;
+
+done:
+ if (num_fields == 0)
+ cache->ready_cb(cache->user_data);
+}
+
+int phonebook_init(void)
+{
+ return 0;
+}
+
+void phonebook_exit(void)
+{
+}
+
+char *phonebook_set_folder(const char *current_folder, const char *new_folder,
+ uint8_t flags, int *err)
+{
+ char *folder;
+
+ if (err)
+ *err = 0;
+
+ folder = g_build_path(current_folder, new_folder, NULL);
+
+ return folder;
+}
+
+int phonebook_pull(const char *name, const struct apparam_field *params,
+ phonebook_cb cb, void *user_data)
+{
+ struct phonebook_data *data;
+
+ data = g_new0(struct phonebook_data, 1);
+ data->vcards = g_string_new(NULL);
+ data->user_data = user_data;
+ data->cb = cb;
+
+ return query_tracker(CONTACTS_QUERY_ALL, 7, pull_contacts, data);
+}
+
+int phonebook_get_entry(const char *folder, const char *id,
+ const struct apparam_field *params,
+ phonebook_cb cb, void *user_data)
+{
+ struct phonebook_data *data;
+ char *query;
+ int ret;
+
+ data = g_new0(struct phonebook_data, 1);
+ data->vcards = g_string_new(NULL);
+ data->user_data = user_data;
+ data->cb = cb;
+
+ query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id, id);
+
+ ret = query_tracker(query, 8, pull_contacts, data);;
+
+ g_free(query);
+
+ return ret;
+}
+
+int phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
+ phonebook_cache_ready_cb ready_cb, void *user_data)
+{
+ struct cache_data *cache;
+
+ cache = g_new0(struct cache_data, 1);
+ cache->entry_cb = entry_cb;
+ cache->ready_cb = ready_cb;
+ cache->user_data = user_data;
+
+ return query_tracker(CONTACTS_QUERY_ALL_LIST, 7, add_to_cache, cache);
+}
diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c
new file mode 100644
index 0000000..41a0995
--- /dev/null
+++ b/obexd/plugins/vcard.c
+/*
+ * OBEX Server
+ *
+ * Copyright (C) 2008-2010 Intel Corporation. All rights reserved.
+ *
+ * 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
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <gdbus.h>
+
+#include "vcard.h"
+
+#define LEN_MAX 128
+#define TYPE_INTERNATIONAL 145
+
+#define PHONEBOOK_FLAG_CACHED 0x1
+
+/* according to RFC 2425, the output string may need folding */
+static void vcard_printf(GString *str, const char *fmt, ...)
+{
+ char buf[1024];
+ va_list ap;
+ int len_temp, line_number, i;
+ unsigned int line_delimit = 75;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ line_number = strlen(buf) / line_delimit + 1;
+
+ for (i = 0; i < line_number; i++) {
+ len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i);
+ g_string_append_len(str, buf + line_delimit * i, len_temp);
+ if (i != line_number - 1)
+ g_string_append(str, "\r\n ");
+ }
+
+ g_string_append(str, "\r\n");
+}
+
+/* According to RFC 2426, we need escape following characters:
+ * '\n', '\r', ';', ',', '\'.
+ */
+static void add_slash(char *dest, const char *src, int len_max, int len)
+{
+ int i, j;
+
+ for (i = 0, j = 0; i < len && j < len_max; i++, j++) {
+ switch (src[i]) {
+ case '\n':
+ dest[j++] = '\\';
+ dest[j] = 'n';
+ break;
+ case '\r':
+ dest[j++] = '\\';
+ dest[j] = 'r';
+ break;
+ case '\\':
+ case ';':
+ case ',':
+ dest[j++] = '\\';
+ default:
+ dest[j] = src[i];
+ break;
+ }
+ }
+ dest[j] = 0;
+ return;
+}
+
+static void vcard_printf_begin(GString *vcards)
+{
+ vcard_printf(vcards, "BEGIN:VCARD");
+ vcard_printf(vcards, "VERSION:3.0");
+}
+
+static void vcard_printf_text(GString *vcards, const char *text)
+{
+ char field[LEN_MAX];
+ add_slash(field, text, LEN_MAX, strlen(text));
+ vcard_printf(vcards, "FN:%s", field);
+}
+
+static void vcard_printf_number(GString *vcards, const char *number, int type,
+ enum phonebook_number_type category)
+{
+ char *pref = "", *intl = "", *category_string = "";
+ char buf[128];
+
+ if (!number || !strlen(number) || !type)
+ return;
+
+ switch (category) {
+ case TEL_TYPE_HOME:
+ category_string = "HOME,VOICE";
+ break;
+ case TEL_TYPE_MOBILE:
+ category_string = "CELL,VOICE";
+ break;
+ case TEL_TYPE_FAX:
+ category_string = "FAX";
+ break;
+ case TEL_TYPE_WORK:
+ category_string = "WORK,VOICE";
+ break;
+ case TEL_TYPE_OTHER:
+ category_string = "VOICE";
+ break;
+ }
+
+ if ((type == TYPE_INTERNATIONAL) && (number[0] != '+'))
+ intl = "+";
+
+ snprintf(buf, sizeof(buf), "TEL;TYPE=\%s%s:\%s\%s", pref,
+ category_string, intl, number);
+ vcard_printf(vcards, buf, number);
+}
+
+static void vcard_printf_email(GString *vcards, const char *email)
+{
+ int len = 0;
+
+ if (email)
+ len = strlen(email);
+
+ if (len) {
+ char field[LEN_MAX];
+ add_slash(field, email, LEN_MAX, len);
+ vcard_printf(vcards,
+ "EMAIL;TYPE=INTERNET:%s", field);
+ }
+}
+
+static void vcard_printf_end(GString *vcards)
+{
+ vcard_printf(vcards, "END:VCARD");
+ vcard_printf(vcards, "");
+}
+
+void phonebook_add_entry(GString *vcards, const char *number, int type,
+ const char *text, const char *email)
+{
+ /* There's really nothing to do */
+ if ((number == NULL || number[0] == '\0') &&
+ (text == NULL || text[0] == '\0'))
+ return;
+
+ vcard_printf_begin(vcards);
+
+ if (text == NULL || text[0] == '\0')
+ vcard_printf_text(vcards, number);
+ else
+ vcard_printf_text(vcards, text);
+
+ vcard_printf_email(vcards, email);
+ vcard_printf_number(vcards, number, type, TEL_TYPE_OTHER);
+ vcard_printf_end(vcards);
+}
diff --git a/obexd/plugins/vcard.h b/obexd/plugins/vcard.h
new file mode 100644
index 0000000..7b47984
--- /dev/null
+++ b/obexd/plugins/vcard.h
+/*
+ * OBEX Server
+ *
+ * Copyright (C) 2008-2010 Intel Corporation. All rights reserved.
+ *
+ * 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
+ *
+ */
+
+enum phonebook_number_type {
+ TEL_TYPE_HOME,
+ TEL_TYPE_MOBILE,
+ TEL_TYPE_FAX,
+ TEL_TYPE_WORK,
+ TEL_TYPE_OTHER,
+};
+
+void phonebook_add_entry(GString *vcards, const char *number, int type,
+ const char *text, const char *email);