<commit_before>/*
 *  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 <time.h>
#include <stdio.h>
#include <errno.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <openobex/obex.h>
#include <openobex/obex_const.h>

#include "log.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 "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-me"
#define AFFILATION_HOME "Home"
#define AFFILATION_WORK "Work"
#define ADDR_FIELD_AMOUNT 7
#define PULL_QUERY_COL_AMOUNT 23
#define COUNT_QUERY_COL_AMOUNT 1

#define COL_PHONE_AFF 0 /* work/home phone numbers */
#define COL_FULL_NAME 1
#define COL_FAMILY_NAME 2
#define COL_GIVEN_NAME 3
#define COL_ADDITIONAL_NAME 4
#define COL_NAME_PREFIX 5
#define COL_NAME_SUFFIX 6
#define COL_ADDR_AFF 7 /* addresses from affilation */
#define COL_BIRTH_DATE 8
#define COL_NICKNAME 9
#define COL_URL 10
#define COL_PHOTO 11
#define COL_ORG_ROLE 12
#define COL_UID 13
#define COL_TITLE 14
#define COL_AFF_TYPE 15
#define COL_ORG_NAME 16
#define COL_ORG_DEPARTMENT 17
#define COL_EMAIL_AFF 18 /* email's from affilation (work/home) */
#define COL_DATE 19
#define COL_SENT 20
#define COL_ANSWERED 21
#define CONTACTS_ID_COL 22
#define CONTACT_ID_PREFIX "contact:"

#define FAX_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#FaxNumber"
#define MOBILE_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#CellPhoneNumber"

#define MAIN_DELIM "\30" /* Main delimiter between phones, addresses, emails*/
#define SUB_DELIM "\31" /* Delimiter used in telephone number strings*/
#define MAX_FIELDS 100 /* Max amount of fields to be concatenated at once*/

#define CONTACTS_QUERY_ALL						\
"SELECT "								\
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"			\
"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"			\
"WHERE {"								\
"	?_role nco:hasPhoneNumber ?aff_number"				\
"}) "									\
"nco:fullname(?_contact) "						\
"nco:nameFamily(?_contact) "						\
"nco:nameGiven(?_contact) "						\
"nco:nameAdditional(?_contact) "					\
"nco:nameHonorificPrefix(?_contact) "					\
"nco:nameHonorificSuffix(?_contact) "					\
"(SELECT GROUP_CONCAT(fn:concat("					\
"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","	\
"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:country(?aff_addr), \"\"), "			\
"\"\31\", rdfs:label(?_role) ), "					\
"\"\30\") "								\
"WHERE {"								\
"?_role nco:hasPostalAddress ?aff_addr"					\
"}) "									\
"nco:birthDate(?_contact) "						\
"nco:nickname(?_contact) "						\
"nco:url(?_contact) "							\
"nie:url(nco:photo(?_contact)) "					\
"nco:role(?_role) "							\
"nco:contactUID(?_contact) "						\
"nco:title(?_role) "							\
"rdfs:label(?_role) "							\
"nco:fullname(nco:org(?_role))"						\
"nco:department(?_role) "						\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
"\"NOTACALL\" \"false\" \"false\" "					\
"?_contact "								\
"WHERE {"								\
"	?_contact a nco:PersonContact ;"				\
"	nco:nameFamily ?_key ."						\
"	OPTIONAL {?_contact nco:hasAffiliation ?_role .}"		\
"}"									\
"ORDER BY ?_key tracker:id(?_contact)"

#define CONTACTS_QUERY_ALL_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
		"?c a nco:PersonContact . "				\
	"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "			\
	"OPTIONAL { "							\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?c"

#define MISSED_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "


#define MISSED_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
	"}UNION{"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define INCOMING_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define INCOMING_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h ."				\
	"}UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"}"								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define OUTGOING_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_unb_contact ; "					\
	"nmo:isSent true . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define OUTGOING_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?c ; "						\
		"nmo:isSent true . "					\
	"} UNION {"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?x ; "						\
		"nmo:isSent true . "					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION {"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?x ; "						\
		"nmo:isSent true . "					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"}"								\
	"} GROUP BY ?call ORDER BY DESC(nmo:sentDate(?call))"

#define COMBINED_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_unb_contact ; "					\
	"nmo:isSent true . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define COMBINED_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) nco:nameGiven(?c) "		\
	"nco:nameAdditional(?c) nco:nameHonorificPrefix(?c) "		\
	"nco:nameHonorificSuffix(?c) nco:phoneNumber(?h) "		\
	"WHERE { "							\
	"	{ "							\
			"?c a nco:Contact . "				\
			"?c nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?c ; "					\
			"nmo:isSent true . "				\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?x ; "					\
			"nmo:isSent true . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasPhoneNumber ?h . "			\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?x ; "					\
			"nmo:isSent true . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasAffiliation ?a . "			\
			"?a nco:hasPhoneNumber ?h . "			\
		"}UNION {"						\
			"?c a nco:Contact . "				\
			"?c nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?c ; "				\
			"nmo:isSent false . "				\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?x ; "				\
			"nmo:isSent false . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasPhoneNumber ?h . "			\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?x ; "				\
			"nmo:isSent false . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasAffiliation ?a . "			\
			"?a nco:hasPhoneNumber ?h . "			\
		"}"							\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define CONTACTS_QUERY_FROM_URI						\
"SELECT "								\
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"			\
"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"			\
"WHERE {"								\
"	?_role nco:hasPhoneNumber ?aff_number"				\
"}) "									\
"nco:fullname(<%s>) "							\
"nco:nameFamily(<%s>) "							\
"nco:nameGiven(<%s>) "							\
"nco:nameAdditional(<%s>) "						\
"nco:nameHonorificPrefix(<%s>) "					\
"nco:nameHonorificSuffix(<%s>) "					\
"(SELECT GROUP_CONCAT(fn:concat("					\
"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","	\
"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:country(?aff_addr), \"\"), "			\
"\"\31\", rdfs:label(?_role) ), "					\
"\"\30\") "								\
"WHERE {"								\
"?_role nco:hasPostalAddress ?aff_addr"					\
"}) "									\
"nco:birthDate(<%s>) "							\
"nco:nickname(<%s>) "							\
"nco:url(<%s>) "							\
"nie:url(nco:photo(<%s>)) "						\
"nco:role(?_role) "							\
"nco:contactUID(<%s>) "							\
"nco:title(?_role) "							\
"rdfs:label(?_role) "							\
"nco:fullname(nco:org(?_role))"						\
"nco:department(?_role) "						\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
"\"NOTACALL\" \"false\" \"false\" "					\
"<%s> "									\
"WHERE {"								\
"	<%s> a nco:PersonContact ;"					\
"	nco:nameFamily ?_key ."						\
"	OPTIONAL {<%s> nco:hasAffiliation ?_role .}"			\
"}"									\
"ORDER BY ?_key tracker:id(<%s>)"

#define CONTACTS_OTHER_QUERY_FROM_URI					\
	"SELECT fn:concat(\"TYPE_OTHER\", \"\31\", nco:phoneNumber(?t))"\
	"\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "			\
	"\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "		\
	" \"NOTACALL\" \"false\" \"false\" <%s> "			\
	"WHERE { "							\
		"<%s> a nco:Contact . "					\
		"OPTIONAL { <%s> nco:hasPhoneNumber ?t . } "		\
	"} "

#define CONTACTS_COUNT_QUERY						\
	"SELECT COUNT(?c) "						\
	"WHERE {"							\
		"?c a nco:PersonContact ."				\
		"FILTER (regex(str(?c), \"contact:\"))"			\
	"}"

#define MISSED_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent false ;"					\
		"nmo:from ?c ;"						\
		"nmo:isAnswered false ."				\
	"}"

#define INCOMING_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent false ;"					\
		"nmo:from ?c ;"						\
		"nmo:isAnswered true ."					\
	"}"

#define OUTGOING_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent true ;"					\
		"nmo:to ?c ."						\
	"}"

#define COMBINED_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
	"{"								\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent true ;"					\
		"nmo:to ?c ."						\
	"}UNION {"							\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:from ?c ."						\
	"}"								\
	"}"

#define NEW_MISSED_CALLS_LIST						\
	"SELECT ?c "							\
	"nco:phoneNumber(?h) "						\
	"nmo:isRead(?call) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
	"}UNION{"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call)) "	\
	"LIMIT 40"

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 contact_data {
	char *id;
	struct phonebook_contact *contact;
};

struct phonebook_data {
	phonebook_cb cb;
	void *user_data;
	int index;
	gboolean vcardentry;
	const struct apparam_field *params;
	GSList *contacts;
	phonebook_cache_ready_cb ready_cb;
	phonebook_entry_cb entry_cb;
	int newmissedcalls;
	DBusPendingCall *call;
};

struct phonebook_index {
	GArray *phonebook;
	int index;
};

static DBusConnection *connection = NULL;

static const char *name2query(const char *name)
{
	if (g_str_equal(name, "telecom/pb.vcf"))
		return CONTACTS_QUERY_ALL;
	else if (g_str_equal(name, "telecom/ich.vcf"))
		return INCOMING_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/och.vcf"))
		return OUTGOING_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/mch.vcf"))
		return MISSED_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/cch.vcf"))
		return COMBINED_CALLS_QUERY;

	return NULL;
}

static const char *name2count_query(const char *name)
{
	if (g_str_equal(name, "telecom/pb.vcf"))
		return CONTACTS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/ich.vcf"))
		return INCOMING_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/och.vcf"))
		return OUTGOING_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/mch.vcf"))
		return MISSED_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/cch.vcf"))
		return COMBINED_CALLS_COUNT_QUERY;

	return NULL;
}

static gboolean folder_is_valid(const char *folder)
{
	if (folder == NULL)
		return FALSE;

	if (g_str_equal(folder, "/"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/pb"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/ich"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/och"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/mch"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/cch"))
		return TRUE;

	return FALSE;
}

static const char *folder2query(const char *folder)
{
	if (g_str_equal(folder, "/telecom/pb"))
		return CONTACTS_QUERY_ALL_LIST;
	else if (g_str_equal(folder, "/telecom/ich"))
		return INCOMING_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/och"))
		return OUTGOING_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/mch"))
		return MISSED_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/cch"))
		return COMBINED_CALLS_LIST;

	return 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) {
			g_free(result);
			return NULL;
		}

		dbus_message_iter_get_basic(&sub, &arg);

		result[i] = arg;

		i++;
		dbus_message_iter_next(&sub);
	}

	return result;
}

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);

	/* pending data is freed in query_free_data after call is unreffed. */
}

static void query_free_data(void *user_data)
{
	struct pending_reply *pending = user_data;

	if (!pending)
		return;

	g_free(pending);
}

static DBusPendingCall *query_tracker(const char *query, int num_fields,
		reply_list_foreach_t callback, void *user_data, int *err)
{
	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);
		if (err)
			*err = -EPERM;
		return NULL;
	}

	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,
							query_free_data);
	dbus_message_unref(msg);

	if (err)
		*err = 0;

	return call;
}

static char *iso8601_utc_to_localtime(const char *datetime)
{
	time_t time;
	struct tm tm, *local;
	char localdate[32];
	char tz;
	int nr;

	memset(&tm, 0, sizeof(tm));

	nr = sscanf(datetime, "%04u-%02u-%02uT%02u:%02u:%02u%c",
			&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
			&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
			&tz);
	if (nr < 6) {
		/* Invalid time format */
		error("sscanf(): %s (%d)", strerror(errno), errno);
		return g_strdup("");
	}

	/* Time already in localtime */
	if (nr == 6) {
		strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", &tm);
		return g_strdup(localdate);
	}

	tm.tm_year -= 1900;	/* Year since 1900 */
	tm.tm_mon--;		/* Months since January, values 0-11 */

	time = mktime(&tm);
	time -= timezone;

	local = localtime(&time);

	strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", local);

	return g_strdup(localdate);
}

static void set_call_type(struct phonebook_contact *contact,
				const char *datetime, const char *is_sent,
				const char *is_answered)
{
	gboolean sent, answered;

	if (g_strcmp0(datetime, "NOTACALL") == 0) {
		contact->calltype = CALL_TYPE_NOT_A_CALL;
		return;
	}

	sent = g_str_equal(is_sent, "true");
	answered = g_str_equal(is_answered, "true");

	if (sent == FALSE) {
		if (answered == FALSE)
			contact->calltype = CALL_TYPE_MISSED;
		else
			contact->calltype = CALL_TYPE_INCOMING;
	} else
		contact->calltype = CALL_TYPE_OUTGOING;

	/* Tracker gives time in the ISO 8601 format, UTC time */
	contact->datetime = iso8601_utc_to_localtime(datetime);
}

static struct phonebook_contact *find_contact(GSList *contacts, const char *id)
{
	GSList *l;

	for (l = contacts; l; l = l->next) {
		struct contact_data *c_data = l->data;
		if (g_strcmp0(c_data->id, id) == 0)
			return c_data->contact;
	}

	return NULL;
}

static struct phonebook_field *find_phone(GSList *numbers, const char *phone,
								int type)
{
	GSList *l;

	for (l = numbers; l; l = l->next) {
		struct phonebook_field *pb_num = l->data;
		/* Returning phonebook number if phone values and type values
		 * are equal */
		if (g_strcmp0(pb_num->text, phone) == 0 && pb_num->type == type)
			return pb_num;
	}

	return NULL;
}

static void add_phone_number(struct phonebook_contact *contact,
						const char *phone, int type)
{
	struct phonebook_field *number;

	if (phone == NULL || strlen(phone) == 0)
		return;

	/* Not adding number if there is already added with the same value */
	if (find_phone(contact->numbers, phone, type))
		return;

	number = g_new0(struct phonebook_field, 1);
	number->text = g_strdup(phone);
	number->type = type;

	contact->numbers = g_slist_append(contact->numbers, number);
}

static struct phonebook_field *find_email(GSList *emails, const char *address,
								int type)
{
	GSList *l;

	for (l = emails; l; l = l->next) {
		struct phonebook_field *email = l->data;
		if (g_strcmp0(email->text, address) == 0 &&
						email->type == type)
			return email;
	}

	return NULL;
}

static void add_email(struct phonebook_contact *contact, const char *address,
								int type)
{
	struct phonebook_field *email;

	if (address == NULL || strlen(address) == 0)
		return;

	/* Not adding email if there is already added with the same value */
	if (find_email(contact->emails, address, type))
		return;

	email = g_new0(struct phonebook_field, 1);
	email->text = g_strdup(address);
	email->type = type;

	contact->emails = g_slist_append(contact->emails, email);
}

static struct phonebook_field *find_address(GSList *addresses,
					const char *address, int type)
{
	GSList *l;

	for (l = addresses; l; l = l->next) {
		struct phonebook_field *addr = l->data;
		if (g_strcmp0(addr->text, address) == 0 &&
						addr->type == type)
			return addr;
	}

	return NULL;
}

static void add_address(struct phonebook_contact *contact,
					const char *address, int type)
{
	struct phonebook_field *addr;

	if (address == NULL || address_fields_present(address) == FALSE)
		return;

	/* Not adding address if there is already added with the same value */
	if (find_address(contact->addresses, address, type))
		return;

	addr = g_new0(struct phonebook_field, 1);

	addr->text = g_strdup(address);
	addr->type = type;

	contact->addresses = g_slist_append(contact->addresses, addr);
}

static GString *gen_vcards(GSList *contacts,
					const struct apparam_field *params)
{
	GSList *l;
	GString *vcards;

	vcards = g_string_new(NULL);

	/* Generating VCARD string from contacts and freeing used contacts */
	for (l = contacts; l; l = l->next) {
		struct contact_data *c_data = l->data;
		phonebook_add_contact(vcards, c_data->contact,
					params->filter, params->format);

		g_free(c_data->id);
		phonebook_contact_free(c_data->contact);
		g_free(c_data);
	}

	return vcards;
}<commit_msg>obexd: Simplify add numbers/emails/addresses logic in PBAP<commit_after>/*
 *  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 <time.h>
#include <stdio.h>
#include <errno.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <openobex/obex.h>
#include <openobex/obex_const.h>

#include "log.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 "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-me"
#define AFFILATION_HOME "Home"
#define AFFILATION_WORK "Work"
#define ADDR_FIELD_AMOUNT 7
#define PULL_QUERY_COL_AMOUNT 23
#define COUNT_QUERY_COL_AMOUNT 1

#define COL_PHONE_AFF 0 /* work/home phone numbers */
#define COL_FULL_NAME 1
#define COL_FAMILY_NAME 2
#define COL_GIVEN_NAME 3
#define COL_ADDITIONAL_NAME 4
#define COL_NAME_PREFIX 5
#define COL_NAME_SUFFIX 6
#define COL_ADDR_AFF 7 /* addresses from affilation */
#define COL_BIRTH_DATE 8
#define COL_NICKNAME 9
#define COL_URL 10
#define COL_PHOTO 11
#define COL_ORG_ROLE 12
#define COL_UID 13
#define COL_TITLE 14
#define COL_AFF_TYPE 15
#define COL_ORG_NAME 16
#define COL_ORG_DEPARTMENT 17
#define COL_EMAIL_AFF 18 /* email's from affilation (work/home) */
#define COL_DATE 19
#define COL_SENT 20
#define COL_ANSWERED 21
#define CONTACTS_ID_COL 22
#define CONTACT_ID_PREFIX "contact:"

#define FAX_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#FaxNumber"
#define MOBILE_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#CellPhoneNumber"

#define MAIN_DELIM "\30" /* Main delimiter between phones, addresses, emails*/
#define SUB_DELIM "\31" /* Delimiter used in telephone number strings*/
#define MAX_FIELDS 100 /* Max amount of fields to be concatenated at once*/

#define CONTACTS_QUERY_ALL						\
"SELECT "								\
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"			\
"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"			\
"WHERE {"								\
"	?_role nco:hasPhoneNumber ?aff_number"				\
"}) "									\
"nco:fullname(?_contact) "						\
"nco:nameFamily(?_contact) "						\
"nco:nameGiven(?_contact) "						\
"nco:nameAdditional(?_contact) "					\
"nco:nameHonorificPrefix(?_contact) "					\
"nco:nameHonorificSuffix(?_contact) "					\
"(SELECT GROUP_CONCAT(fn:concat("					\
"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","	\
"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:country(?aff_addr), \"\"), "			\
"\"\31\", rdfs:label(?_role) ), "					\
"\"\30\") "								\
"WHERE {"								\
"?_role nco:hasPostalAddress ?aff_addr"					\
"}) "									\
"nco:birthDate(?_contact) "						\
"nco:nickname(?_contact) "						\
"nco:url(?_contact) "							\
"nie:url(nco:photo(?_contact)) "					\
"nco:role(?_role) "							\
"nco:contactUID(?_contact) "						\
"nco:title(?_role) "							\
"rdfs:label(?_role) "							\
"nco:fullname(nco:org(?_role))"						\
"nco:department(?_role) "						\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
"\"NOTACALL\" \"false\" \"false\" "					\
"?_contact "								\
"WHERE {"								\
"	?_contact a nco:PersonContact ;"				\
"	nco:nameFamily ?_key ."						\
"	OPTIONAL {?_contact nco:hasAffiliation ?_role .}"		\
"}"									\
"ORDER BY ?_key tracker:id(?_contact)"

#define CONTACTS_QUERY_ALL_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
		"?c a nco:PersonContact . "				\
	"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "			\
	"OPTIONAL { "							\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?c"

#define MISSED_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isAnswered false ;"					\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "


#define MISSED_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
	"}UNION{"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define INCOMING_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isAnswered true ;"						\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define INCOMING_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h ."				\
	"}UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered true ."					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"}"								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define OUTGOING_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_unb_contact ; "					\
	"nmo:isSent true . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define OUTGOING_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) "					\
	"nco:nameGiven(?c) nco:nameAdditional(?c) "			\
	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
	"nco:phoneNumber(?h) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?c ; "						\
		"nmo:isSent true . "					\
	"} UNION {"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?x ; "						\
		"nmo:isSent true . "					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION {"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:to ?x ; "						\
		"nmo:isSent true . "					\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"}"								\
	"} GROUP BY ?call ORDER BY DESC(nmo:sentDate(?call))"

#define COMBINED_CALLS_QUERY						\
"SELECT "								\
"(SELECT fn:concat(rdf:type(?role_number),"				\
	"\"\31\", nco:phoneNumber(?role_number))"			\
	"WHERE {"							\
	"{"								\
	"	?_role nco:hasPhoneNumber ?role_number "		\
	"	FILTER (?role_number = ?_number)"			\
	"} UNION { "							\
		"?_unb_contact nco:hasPhoneNumber ?role_number . "	\
	"}"								\
"} GROUP BY nco:phoneNumber(?role_number) ) "				\
	"nco:fullname(?_contact) "					\
	"nco:nameFamily(?_contact) "					\
	"nco:nameGiven(?_contact) "					\
	"nco:nameAdditional(?_contact) "				\
	"nco:nameHonorificPrefix(?_contact) "				\
	"nco:nameHonorificSuffix(?_contact) "				\
"(SELECT GROUP_CONCAT(fn:concat("					\
	"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","\
	"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","		\
	"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","	\
	"tracker:coalesce(nco:country(?aff_addr), \"\"), "		\
	"\"\31\", rdfs:label(?c_role) ), "				\
	"\"\30\") "							\
	"WHERE {"							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasPostalAddress ?aff_addr"			\
	"}) "								\
	"nco:birthDate(?_contact) "					\
	"nco:nickname(?_contact) "					\
	"nco:url(?_contact) "						\
	"nie:url(nco:photo(?_contact)) "				\
	"nco:role(?_role) "						\
	"nco:contactUID(?_contact) "					\
	"nco:title(?_role) "						\
	"rdfs:label(?_role) "						\
	"nco:fullname(nco:org(?_role)) "				\
	"nco:department(?_role) "					\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?c_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_contact nco:hasAffiliation ?c_role . "			\
	"?c_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
	"nmo:receivedDate(?_call) "					\
	"nmo:isSent(?_call) "						\
	"nmo:isAnswered(?_call) "					\
	"fn:concat(tracker:coalesce(?_ncontact, \"\"),"			\
	"tracker:coalesce(?_unb_contact, \"\"))"			\
	" "								\
"WHERE { "								\
"{ "									\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_ncontact ; "						\
	"nmo:isSent true . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:to ?_unb_contact ; "					\
	"nmo:isSent true . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact ; "				\
	"nco:hasPhoneNumber ?_number . "				\
	"OPTIONAL { ?_contact nco:hasAffiliation ?_role .} "		\
"} UNION { "								\
	"?_ncontact a nco:Contact . "					\
	"?_ncontact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_ncontact ; "					\
	"nmo:isSent false . "						\
	"?_contact a nco:PersonContact . "				\
	"?_contact nco:hasAffiliation ?_role . "			\
	"?_role nco:hasPhoneNumber ?_number . "				\
"} UNION { "								\
	"?_unb_contact a nco:Contact . "				\
	"?_unb_contact nco:hasPhoneNumber ?_number . "			\
	"?_call a nmo:Call ; "						\
	"nmo:from ?_unb_contact ; "					\
	"nmo:isSent false . "						\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasPhoneNumber ?_number . } "				\
	"OPTIONAL {?_contact a nco:PersonContact ; "			\
	"nco:hasAffiliation ?_role . "					\
	"?_role nco:hasPhoneNumber ?_number. } "			\
	"FILTER ( !bound(?_contact) && !bound(?_role) ) "		\
"} "									\
"} "									\
"ORDER BY DESC(nmo:sentDate(?_call)) "

#define COMBINED_CALLS_LIST						\
	"SELECT ?c nco:nameFamily(?c) nco:nameGiven(?c) "		\
	"nco:nameAdditional(?c) nco:nameHonorificPrefix(?c) "		\
	"nco:nameHonorificSuffix(?c) nco:phoneNumber(?h) "		\
	"WHERE { "							\
	"	{ "							\
			"?c a nco:Contact . "				\
			"?c nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?c ; "					\
			"nmo:isSent true . "				\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?x ; "					\
			"nmo:isSent true . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasPhoneNumber ?h . "			\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:to ?x ; "					\
			"nmo:isSent true . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasAffiliation ?a . "			\
			"?a nco:hasPhoneNumber ?h . "			\
		"}UNION {"						\
			"?c a nco:Contact . "				\
			"?c nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?c ; "				\
			"nmo:isSent false . "				\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?x ; "				\
			"nmo:isSent false . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasPhoneNumber ?h . "			\
		"} UNION {"						\
			"?x a nco:Contact . "				\
			"?x nco:hasPhoneNumber ?h . "			\
			"?call a nmo:Call ; "				\
			"nmo:from ?x ; "				\
			"nmo:isSent false . "				\
			"?c a nco:PersonContact . "			\
			"?c nco:hasAffiliation ?a . "			\
			"?a nco:hasPhoneNumber ?h . "			\
		"}"							\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"

#define CONTACTS_QUERY_FROM_URI						\
"SELECT "								\
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"			\
"\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"			\
"WHERE {"								\
"	?_role nco:hasPhoneNumber ?aff_number"				\
"}) "									\
"nco:fullname(<%s>) "							\
"nco:nameFamily(<%s>) "							\
"nco:nameGiven(<%s>) "							\
"nco:nameAdditional(<%s>) "						\
"nco:nameHonorificPrefix(<%s>) "					\
"nco:nameHonorificSuffix(<%s>) "					\
"(SELECT GROUP_CONCAT(fn:concat("					\
"tracker:coalesce(nco:pobox(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \";\","	\
"tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:locality(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:region(?aff_addr), \"\"), \";\","			\
"tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \";\","		\
"tracker:coalesce(nco:country(?aff_addr), \"\"), "			\
"\"\31\", rdfs:label(?_role) ), "					\
"\"\30\") "								\
"WHERE {"								\
"?_role nco:hasPostalAddress ?aff_addr"					\
"}) "									\
"nco:birthDate(<%s>) "							\
"nco:nickname(<%s>) "							\
"nco:url(<%s>) "							\
"nie:url(nco:photo(<%s>)) "						\
"nco:role(?_role) "							\
"nco:contactUID(<%s>) "							\
"nco:title(?_role) "							\
"rdfs:label(?_role) "							\
"nco:fullname(nco:org(?_role))"						\
"nco:department(?_role) "						\
"(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\","			\
	"rdfs:label(?_role)),"						\
	"\"\30\") "							\
	"WHERE { "							\
	"?_role nco:hasEmailAddress "					\
	"		[ nco:emailAddress ?emailaddress ] "		\
	"}) "								\
"\"NOTACALL\" \"false\" \"false\" "					\
"<%s> "									\
"WHERE {"								\
"	<%s> a nco:PersonContact ;"					\
"	nco:nameFamily ?_key ."						\
"	OPTIONAL {<%s> nco:hasAffiliation ?_role .}"			\
"}"									\
"ORDER BY ?_key tracker:id(<%s>)"

#define CONTACTS_OTHER_QUERY_FROM_URI					\
	"SELECT fn:concat(\"TYPE_OTHER\", \"\31\", nco:phoneNumber(?t))"\
	"\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "			\
	"\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "		\
	" \"NOTACALL\" \"false\" \"false\" <%s> "			\
	"WHERE { "							\
		"<%s> a nco:Contact . "					\
		"OPTIONAL { <%s> nco:hasPhoneNumber ?t . } "		\
	"} "

#define CONTACTS_COUNT_QUERY						\
	"SELECT COUNT(?c) "						\
	"WHERE {"							\
		"?c a nco:PersonContact ."				\
		"FILTER (regex(str(?c), \"contact:\"))"			\
	"}"

#define MISSED_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent false ;"					\
		"nmo:from ?c ;"						\
		"nmo:isAnswered false ."				\
	"}"

#define INCOMING_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent false ;"					\
		"nmo:from ?c ;"						\
		"nmo:isAnswered true ."					\
	"}"

#define OUTGOING_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent true ;"					\
		"nmo:to ?c ."						\
	"}"

#define COMBINED_CALLS_COUNT_QUERY					\
	"SELECT COUNT(?call) WHERE {"					\
	"{"								\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:isSent true ;"					\
		"nmo:to ?c ."						\
	"}UNION {"							\
		"?c a nco:Contact ;"					\
		"nco:hasPhoneNumber ?h ."				\
		"?call a nmo:Call ;"					\
		"nmo:from ?c ."						\
	"}"								\
	"}"

#define NEW_MISSED_CALLS_LIST						\
	"SELECT ?c "							\
	"nco:phoneNumber(?h) "						\
	"nmo:isRead(?call) "						\
	"WHERE { "							\
	"{"								\
		"?c a nco:Contact . "					\
		"?c nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?c ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
	"}UNION{"							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasPhoneNumber ?h . "				\
	"} UNION { "							\
		"?x a nco:Contact . "					\
		"?x nco:hasPhoneNumber ?h . "				\
		"?call a nmo:Call ; "					\
		"nmo:from ?x ; "					\
		"nmo:isSent false ; "					\
		"nmo:isAnswered false ."				\
		"?c a nco:PersonContact . "				\
		"?c nco:hasAffiliation ?a . "				\
		"?a nco:hasPhoneNumber ?h . "				\
	"} "								\
	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call)) "	\
	"LIMIT 40"

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 contact_data {
	char *id;
	struct phonebook_contact *contact;
};

struct phonebook_data {
	phonebook_cb cb;
	void *user_data;
	int index;
	gboolean vcardentry;
	const struct apparam_field *params;
	GSList *contacts;
	phonebook_cache_ready_cb ready_cb;
	phonebook_entry_cb entry_cb;
	int newmissedcalls;
	DBusPendingCall *call;
};

struct phonebook_index {
	GArray *phonebook;
	int index;
};

static DBusConnection *connection = NULL;

static const char *name2query(const char *name)
{
	if (g_str_equal(name, "telecom/pb.vcf"))
		return CONTACTS_QUERY_ALL;
	else if (g_str_equal(name, "telecom/ich.vcf"))
		return INCOMING_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/och.vcf"))
		return OUTGOING_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/mch.vcf"))
		return MISSED_CALLS_QUERY;
	else if (g_str_equal(name, "telecom/cch.vcf"))
		return COMBINED_CALLS_QUERY;

	return NULL;
}

static const char *name2count_query(const char *name)
{
	if (g_str_equal(name, "telecom/pb.vcf"))
		return CONTACTS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/ich.vcf"))
		return INCOMING_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/och.vcf"))
		return OUTGOING_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/mch.vcf"))
		return MISSED_CALLS_COUNT_QUERY;
	else if (g_str_equal(name, "telecom/cch.vcf"))
		return COMBINED_CALLS_COUNT_QUERY;

	return NULL;
}

static gboolean folder_is_valid(const char *folder)
{
	if (folder == NULL)
		return FALSE;

	if (g_str_equal(folder, "/"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/pb"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/ich"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/och"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/mch"))
		return TRUE;
	else if (g_str_equal(folder, "/telecom/cch"))
		return TRUE;

	return FALSE;
}

static const char *folder2query(const char *folder)
{
	if (g_str_equal(folder, "/telecom/pb"))
		return CONTACTS_QUERY_ALL_LIST;
	else if (g_str_equal(folder, "/telecom/ich"))
		return INCOMING_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/och"))
		return OUTGOING_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/mch"))
		return MISSED_CALLS_LIST;
	else if (g_str_equal(folder, "/telecom/cch"))
		return COMBINED_CALLS_LIST;

	return 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) {
			g_free(result);
			return NULL;
		}

		dbus_message_iter_get_basic(&sub, &arg);

		result[i] = arg;

		i++;
		dbus_message_iter_next(&sub);
	}

	return result;
}

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);

	/* pending data is freed in query_free_data after call is unreffed. */
}

static void query_free_data(void *user_data)
{
	struct pending_reply *pending = user_data;

	if (!pending)
		return;

	g_free(pending);
}

static DBusPendingCall *query_tracker(const char *query, int num_fields,
		reply_list_foreach_t callback, void *user_data, int *err)
{
	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);
		if (err)
			*err = -EPERM;
		return NULL;
	}

	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,
							query_free_data);
	dbus_message_unref(msg);

	if (err)
		*err = 0;

	return call;
}

static char *iso8601_utc_to_localtime(const char *datetime)
{
	time_t time;
	struct tm tm, *local;
	char localdate[32];
	char tz;
	int nr;

	memset(&tm, 0, sizeof(tm));

	nr = sscanf(datetime, "%04u-%02u-%02uT%02u:%02u:%02u%c",
			&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
			&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
			&tz);
	if (nr < 6) {
		/* Invalid time format */
		error("sscanf(): %s (%d)", strerror(errno), errno);
		return g_strdup("");
	}

	/* Time already in localtime */
	if (nr == 6) {
		strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", &tm);
		return g_strdup(localdate);
	}

	tm.tm_year -= 1900;	/* Year since 1900 */
	tm.tm_mon--;		/* Months since January, values 0-11 */

	time = mktime(&tm);
	time -= timezone;

	local = localtime(&time);

	strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", local);

	return g_strdup(localdate);
}

static void set_call_type(struct phonebook_contact *contact,
				const char *datetime, const char *is_sent,
				const char *is_answered)
{
	gboolean sent, answered;

	if (g_strcmp0(datetime, "NOTACALL") == 0) {
		contact->calltype = CALL_TYPE_NOT_A_CALL;
		return;
	}

	sent = g_str_equal(is_sent, "true");
	answered = g_str_equal(is_answered, "true");

	if (sent == FALSE) {
		if (answered == FALSE)
			contact->calltype = CALL_TYPE_MISSED;
		else
			contact->calltype = CALL_TYPE_INCOMING;
	} else
		contact->calltype = CALL_TYPE_OUTGOING;

	/* Tracker gives time in the ISO 8601 format, UTC time */
	contact->datetime = iso8601_utc_to_localtime(datetime);
}

static struct phonebook_contact *find_contact(GSList *contacts, const char *id)
{
	GSList *l;

	for (l = contacts; l; l = l->next) {
		struct contact_data *c_data = l->data;
		if (g_strcmp0(c_data->id, id) == 0)
			return c_data->contact;
	}

	return NULL;
}

static struct phonebook_field *find_field(GSList *fields, const char *value,
								int type)
{
	GSList *l;

	for (l = fields; l; l = l->next) {
		struct phonebook_field *field = l->data;
		/* Returning phonebook number if phone values and type values
		 * are equal */
		if (g_strcmp0(field->text, value) == 0 && field->type == type)
			return field;
	}

	return NULL;
}

static void add_phone_number(struct phonebook_contact *contact,
						const char *phone, int type)
{
	struct phonebook_field *number;

	if (phone == NULL || strlen(phone) == 0)
		return;

	/* Not adding number if there is already added with the same value */
	if (find_field(contact->numbers, phone, type))
		return;

	number = g_new0(struct phonebook_field, 1);
	number->text = g_strdup(phone);
	number->type = type;

	contact->numbers = g_slist_append(contact->numbers, number);
}

static void add_email(struct phonebook_contact *contact, const char *address,
								int type)
{
	struct phonebook_field *email;

	if (address == NULL || strlen(address) == 0)
		return;

	/* Not adding email if there is already added with the same value */
	if (find_field(contact->emails, address, type))
		return;

	email = g_new0(struct phonebook_field, 1);
	email->text = g_strdup(address);
	email->type = type;

	contact->emails = g_slist_append(contact->emails, email);
}

static void add_address(struct phonebook_contact *contact,
					const char *address, int type)
{
	struct phonebook_field *addr;

	if (address == NULL || address_fields_present(address) == FALSE)
		return;

	/* Not adding address if there is already added with the same value */
	if (find_field(contact->addresses, address, type))
		return;

	addr = g_new0(struct phonebook_field, 1);

	addr->text = g_strdup(address);
	addr->type = type;

	contact->addresses = g_slist_append(contact->addresses, addr);
}

static GString *gen_vcards(GSList *contacts,
					const struct apparam_field *params)
{
	GSList *l;
	GString *vcards;

	vcards = g_string_new(NULL);

	/* Generating VCARD string from contacts and freeing used contacts */
	for (l = contacts; l; l = l->next) {
		struct contact_data *c_data = l->data;
		phonebook_add_contact(vcards, c_data->contact,
					params->filter, params->format);

		g_free(c_data->id);
		phonebook_contact_free(c_data->contact);
		g_free(c_data);
	}

	return vcards;
}
{'fraction_non_alphanumeric': 0.18342256844883148, 'fraction_numerical': 0.005734730645284072, 'mean_word_length': 2.744726339794755, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 153, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1764707', 'n_tokens_mistral': 39892, 'n_tokens_neox': 33672, 'n_words': 5206}