blob: cc4b49f0e4e90ef321887ea9fedd7b02c550240f [file] [log] [blame]
/*
* 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 26
#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_EMAIL_CONTACT 7 /*email's for other category */
#define COL_ADDR_AFF 8 /* addresses from affilation */
#define COL_ADDR_CONTACT 9 /* addresses from contacts */
#define COL_PHONE_CONTACT 10 /* phone numbers from contact's */
#define COL_BIRTH_DATE 11
#define COL_NICKNAME 12
#define COL_URL 13
#define COL_PHOTO 14
#define COL_ORG_ROLE 15
#define COL_UID 16
#define COL_TITLE 17
#define COL_AFF_TYPE 18
#define COL_ORG_NAME 19
#define COL_ORG_DEPARTMENT 20
#define COL_EMAIL_AFF 21 /* email's from affilation (work/home) */
#define COL_DATE 22
#define COL_SENT 23
#define COL_ANSWERED 24
#define CONTACTS_ID_COL 25
#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(" \
"nco:phoneNumber(?number), \"\30\")" \
"WHERE {" \
" ?_role nco:hasPhoneNumber ?number" \
"}) " \
"nco:fullname(?_contact) " \
"nco:nameFamily(?_contact) " \
"nco:nameGiven(?_contact) " \
"nco:nameAdditional(?_contact) " \
"nco:nameHonorificPrefix(?_contact) " \
"nco:nameHonorificSuffix(?_contact) " \
"(SELECT GROUP_CONCAT(?emailaddress_other, \"\30\") " \
"WHERE {" \
"?_contact nco:hasEmailAddress " \
"[nco:emailAddress ?emailaddress_other]" \
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
" ?_contact nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number)), \"\30\")" \
"WHERE {" \
" ?_contact nco:hasPhoneNumber ?contact_number" \
"}) " \
"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(?emailaddress, \"\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 nco:phoneNumber(?role_number) " \
"WHERE {" \
"?_role nco:hasPhoneNumber ?role_number " \
"FILTER (?role_number = ?_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(?emailaddress_other, \"\30\") " \
"WHERE {" \
"?_contact nco:hasEmailAddress " \
"[nco:emailAddress ?emailaddress_other]" \
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\","\
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
"?_contact nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number))" \
"WHERE {" \
"{" \
" ?_contact nco:hasPhoneNumber ?contact_number . " \
" FILTER (?contact_number = ?_number) " \
" } UNION { " \
" ?_unb_contact nco:hasPhoneNumber ?contact_number . " \
" } " \
"}GROUP BY nco:phoneNumber(?contact_number) ) " \
"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(?emailaddress, \"\30\") " \
"WHERE { " \
"?_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 .} " \
"?_contact nco:nameFamily ?_key ." \
"} 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:nameFamily ?_key . " \
"?_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 nco:phoneNumber(?role_number) " \
"WHERE {" \
" ?_role nco:hasPhoneNumber ?role_number" \
" FILTER (?role_number = ?_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(?emailaddress_other, \"\30\") " \
"WHERE {" \
"?_contact nco:hasEmailAddress " \
"[nco:emailAddress ?emailaddress_other]" \
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\","\
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
"?_contact nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number))" \
"WHERE {" \
"{" \
" ?_contact nco:hasPhoneNumber ?contact_number . " \
" FILTER (?contact_number = ?_number) " \
" } UNION { " \
" ?_unb_contact nco:hasPhoneNumber ?contact_number . " \
" } " \
"}GROUP BY nco:phoneNumber(?contact_number) ) " \
"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(?emailaddress, \"\30\") " \
"WHERE { " \
"?_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 .} " \
"?_contact nco:nameFamily ?_key ." \
"} 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:nameFamily ?_key . " \
"?_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 nco:phoneNumber(?role_number) " \
"WHERE {" \
" ?_role nco:hasPhoneNumber ?role_number" \
" FILTER (?role_number = ?_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(?emailaddress_other, \"\30\") " \
"WHERE {" \
"?_contact nco:hasEmailAddress " \
"[nco:emailAddress ?emailaddress_other]" \
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\","\
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
"?_contact nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number))" \
"WHERE {" \
"{" \
" ?_contact nco:hasPhoneNumber ?contact_number . " \
" FILTER (?contact_number = ?_number) " \
" } UNION { " \
" ?_unb_contact nco:hasPhoneNumber ?contact_number . " \
" } " \
"}GROUP BY nco:phoneNumber(?contact_number) ) " \
"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(?emailaddress, \"\30\") " \
"WHERE { " \
"?_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 .} " \
"?_contact nco:nameFamily ?_key ." \
"} 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:nameFamily ?_key . " \
"?_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 nco:phoneNumber(?role_number) " \
"WHERE {" \
" ?_role nco:hasPhoneNumber ?role_number" \
" FILTER (?role_number = ?_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(?emailaddress_other, \"\30\") " \
"WHERE {" \
"?_contact nco:hasEmailAddress " \
"[nco:emailAddress ?emailaddress_other]" \
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\","\
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
"?_contact nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number))" \
"WHERE {" \
"{" \
" ?_contact nco:hasPhoneNumber ?contact_number . " \
" FILTER (?contact_number = ?_number) " \
" } UNION { " \
" ?_unb_contact nco:hasPhoneNumber ?contact_number . " \
" } " \
"}GROUP BY nco:phoneNumber(?contact_number) ) " \
"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(?emailaddress, \"\30\") " \
"WHERE { " \
"?_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 .} " \
"?_contact nco:nameFamily ?_key ." \
"} 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:nameFamily ?_key . " \
"?_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 .} " \
"?_contact nco:nameFamily ?_key ." \
"} 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:nameFamily ?_key . " \
"?_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(" \
"nco:phoneNumber(?number), \"\30\")" \
"WHERE {" \
" ?_role nco:hasPhoneNumber ?number" \
"}) " \
"nco:fullname(<%s>) " \
"nco:nameFamily(<%s>) " \
"nco:nameGiven(<%s>) " \
"nco:nameAdditional(<%s>) " \
"nco:nameHonorificPrefix(<%s>) " \
"nco:nameHonorificSuffix(<%s>) " \
"(SELECT GROUP_CONCAT(?emailaddress_other, \"\30\")" \
"WHERE {" \
" <%s> nco:hasEmailAddress [nco:emailAddress ?emailaddress_other]"\
"}) " \
"(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), \"\") ),\"\30\")" \
"WHERE {" \
"?_role nco:hasPostalAddress ?aff_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(" \
"tracker:coalesce(nco:pobox(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:extendedAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:streetAddress(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:locality(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:region(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:postalcode(?oth_addr), \"\"), \";\"," \
"tracker:coalesce(nco:country(?oth_addr), \"\") ),\"\30\")" \
"WHERE {" \
" <%s> nco:hasPostalAddress ?oth_addr" \
"}) " \
"(SELECT GROUP_CONCAT(fn:concat(rdf:type(?contact_number)," \
"\"\31\", nco:phoneNumber(?contact_number)), \"\30\")" \
"WHERE {" \
" <%s> nco:hasPhoneNumber ?contact_number" \
"}) " \
"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(?emailaddress, \"\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:\") || " \
"regex(str(?c), \"nco#default-contact-me\"))" \
"}"
#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 ." \
"}" \
"}"
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;
GDestroyNotify destroy;
};
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;
};
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.
* Same holds for pending->user_data which is not freed in callback
* but in query_free_data.
*/
}
static void query_free_data(void *user_data)
{
struct pending_reply *pending = user_data;
if (!pending)
return;
if (pending->destroy)
pending->destroy(pending->user_data);
g_free(pending);
}
static DBusPendingCall *query_tracker(const char *query, int num_fields,
reply_list_foreach_t callback, void *user_data,
GDestroyNotify destroy, 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;
/* user_data is freed otherwise only if call was sent */
g_free(user_data);
return NULL;
}
pending = g_new0(struct pending_reply, 1);
pending->callback = callback;
pending->user_data = user_data;
pending->num_fields = num_fields;
pending->destroy = destroy;
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_number *find_phone(GSList *numbers, const char *phone,
int type)
{
GSList *l;
for (l = numbers; l; l = l->next) {
struct phonebook_number *pb_num = l->data;
/* Returning phonebook number if phone values and type values
* are equal */
if (g_strcmp0(pb_num->tel, 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_number *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_number, 1);
number->tel = g_strdup(phone);
number->type = type;
contact->numbers = g_slist_append(contact->numbers, number);
}
static struct phonebook_email *find_email(GSList *emails, const char *address,
int type)
{
GSList *l;
for (l = emails; l; l = l->next) {
struct phonebook_email *email = l->data;
if (g_strcmp0(email->address, address) == 0 &&
email->type == type)
return email;
}
return NULL;
}
static void add_email(struct phonebook_contact *contact, const char *address,
int type)
{
struct phonebook_email *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_email, 1);
email->address = g_strdup(address);
email->type = type;
contact->emails = g_slist_append(contact->emails, email);
}
static struct phonebook_address *find_address(GSList *addresses,
const char *address, int type)
{
GSList *l;
for (l = addresses; l; l = l->next) {
struct phonebook_address *addr = l->data;
if (g_strcmp0(addr->addr, address) == 0 &&
addr->type == type)
return addr;
}
return NULL;
}
static void add_address(struct phonebook_contact *contact,
const char *address, int type)
{
struct phonebook_address *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_address, 1);
addr->addr = 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;
}
static void pull_contacts_size(char **reply, int num_fields, void *user_data)
{
struct phonebook_data *data = user_data;
if (num_fields < 0) {
data->cb(NULL, 0, num_fields, 0, data->user_data);
return;
}
if (reply != NULL) {
data->index = atoi(reply[0]);
return;
}
data->cb(NULL, 0, data->index, 0, data->user_data);
/*
* phonebook_data is freed in query_free_data after call is unreffed.
* It is accessible by pointer from data (pending) associated to call.
* Useful in cases when call was terminated.
*/
}
static void add_affiliation(char **field, const char *value)
{
if (strlen(*field) > 0 || value == NULL || strlen(value) == 0)
return;
g_free(*field);
*field = g_strdup(value);
}
static void contact_init(struct phonebook_contact *contact, char **reply)
{
contact->fullname = g_strdup(reply[COL_FULL_NAME]);
contact->family = g_strdup(reply[COL_FAMILY_NAME]);
contact->given = g_strdup(reply[COL_GIVEN_NAME]);
contact->additional = g_strdup(reply[COL_ADDITIONAL_NAME]);
contact->prefix = g_strdup(reply[COL_NAME_PREFIX]);
contact->suffix = g_strdup(reply[COL_NAME_SUFFIX]);
contact->birthday = g_strdup(reply[COL_BIRTH_DATE]);
contact->nickname = g_strdup(reply[COL_NICKNAME]);
contact->website = g_strdup(reply[COL_URL]);
contact->photo = g_strdup(reply[COL_PHOTO]);
contact->company = g_strdup(reply[COL_ORG_NAME]);
contact->department = g_strdup(reply[COL_ORG_DEPARTMENT]);
contact->role = g_strdup(reply[COL_ORG_ROLE]);
contact->uid = g_strdup(reply[COL_UID]);
contact->title = g_strdup(reply[COL_TITLE]);
set_call_type(contact, reply[COL_DATE], reply[COL_SENT],
reply[COL_ANSWERED]);
}
static enum phonebook_number_type get_phone_type(const char *affilation)
{
if (g_strcmp0(AFFILATION_HOME, affilation) == 0)
return TEL_TYPE_HOME;
else if (g_strcmp0(AFFILATION_WORK, affilation) == 0)
return TEL_TYPE_WORK;
return TEL_TYPE_OTHER;
}
static void add_main_number(struct phonebook_contact *contact, char *pnumber)
{
char **num_parts;
char *type, *number;
/* For phone taken directly from contacts data, phone number string
* is represented as number type and number string - those strings are
* separated by SUB_DELIM string */
num_parts = g_strsplit(pnumber, SUB_DELIM, 2);
if (!num_parts)
return;
if (num_parts[0])
type = num_parts[0];
else
goto failed;
if (num_parts[1])
number = num_parts[1];
else
goto failed;
if (g_strrstr(type, FAX_NUM_TYPE))
add_phone_number(contact, number, TEL_TYPE_FAX);
else if (g_strrstr(type, MOBILE_NUM_TYPE))
add_phone_number(contact, number, TEL_TYPE_MOBILE);
else
add_phone_number(contact, number, TEL_TYPE_OTHER);
failed:
g_strfreev(num_parts);
}
static void contact_add_numbers(struct phonebook_contact *contact,
char **reply)
{
char **aff_numbers, **con_numbers;
int i;
/* Filling phonegit numbers from contact's affilation */
aff_numbers = g_strsplit(reply[COL_PHONE_AFF], MAIN_DELIM, MAX_FIELDS);
if (aff_numbers)
for(i = 0;aff_numbers[i]; ++i)
add_phone_number(contact, aff_numbers[i],
get_phone_type(reply[COL_AFF_TYPE]));
g_strfreev(aff_numbers);
/* Filling phone numbers directly from contact's struct */
con_numbers = g_strsplit(reply[COL_PHONE_CONTACT], MAIN_DELIM,
MAX_FIELDS);
if (con_numbers)
for(i = 0; con_numbers[i] != NULL; ++i)
add_main_number(contact, con_numbers[i]);
g_strfreev(con_numbers);
}
static enum phonebook_email_type get_email_type(const char *affilation)
{
if (g_strcmp0(AFFILATION_HOME, affilation) == 0)
return EMAIL_TYPE_HOME;
else if (g_strcmp0(AFFILATION_WORK, affilation) == 0)
return EMAIL_TYPE_WORK;
return EMAIL_TYPE_OTHER;
}
static void contact_add_emails(struct phonebook_contact *contact,
char **reply)
{
char **aff_emails, **con_emails;
int i;
/* Emails from affilation */
aff_emails = g_strsplit(reply[COL_EMAIL_AFF], MAIN_DELIM, MAX_FIELDS);
if (aff_emails)
for(i = 0; aff_emails[i] != NULL; ++i)
add_email(contact, aff_emails[i],
get_email_type(reply[COL_AFF_TYPE]));
g_strfreev(aff_emails);
/* Emails taken directly from contact's data have always type OTHER */
con_emails = g_strsplit(reply[COL_EMAIL_CONTACT], MAIN_DELIM,
MAX_FIELDS);
if (con_emails)
for(i = 0; con_emails[i] != NULL; ++i)
add_email(contact, con_emails[i], EMAIL_TYPE_OTHER);
g_strfreev(con_emails);
}
static enum phonebook_address_type get_addr_type(const char *affilation)
{
if (g_strcmp0(AFFILATION_HOME, affilation) == 0)
return ADDR_TYPE_HOME;
else if (g_strcmp0(AFFILATION_WORK, affilation) == 0)
return ADDR_TYPE_WORK;
return ADDR_TYPE_HOME;
}
static void contact_add_addresses(struct phonebook_contact *contact,
char **reply)
{
char **aff_addr, **con_addr;
int i;
/* Addresses from affilation */
aff_addr = g_strsplit(reply[COL_ADDR_AFF], MAIN_DELIM,
MAX_FIELDS);
if (aff_addr)
for(i = 0; aff_addr[i] != NULL; ++i)
add_address(contact, aff_addr[i],
get_addr_type(reply[COL_AFF_TYPE]));
g_strfreev(aff_addr);
/* Addresses from contact struct */
con_addr = g_strsplit(reply[COL_ADDR_CONTACT], MAIN_DELIM,
MAX_FIELDS);
if (con_addr)
for(i = 0; con_addr[i] != NULL; ++i)
add_address(contact, con_addr[i], ADDR_TYPE_OTHER);
g_strfreev(con_addr);
}
static void contact_add_organization(struct phonebook_contact *contact,
char **reply)
{
/* Adding fields connected by nco:hasAffiliation - they may be in
* separate replies */
add_affiliation(&contact->title, reply[COL_TITLE]);
add_affiliation(&contact->company, reply[COL_ORG_NAME]);
add_affiliation(&contact->department, reply[COL_ORG_DEPARTMENT]);
add_affiliation(&contact->role, reply[COL_ORG_ROLE]);
}
static void pull_contacts(char **reply, int num_fields, void *user_data)
{
struct phonebook_data *data = user_data;
const struct apparam_field *params = data->params;
struct phonebook_contact *contact;
struct contact_data *contact_data;
GString *vcards;
int last_index, i;
gboolean cdata_present = FALSE;
static char *temp_id = NULL;
if (num_fields < 0) {
data->cb(NULL, 0, num_fields, 0, data->user_data);
goto fail;
}
DBG("reply %p", reply);
if (reply == NULL)
goto done;
/* Trying to find contact in recently added contacts. It is needed for
* contacts that have more than one telephone number filled */
contact = find_contact(data->contacts, reply[CONTACTS_ID_COL]);
/* If contact is already created then adding only new phone numbers */
if (contact) {
cdata_present = TRUE;
goto add_numbers;
}
/* We are doing a PullvCardEntry, no need for those checks */
if (data->vcardentry)
goto add_entry;
/* Last four fields are always present, ignoring them */
for (i = 0; i < num_fields - 4; i++) {
if (reply[i][0] != '\0')
break;
}
if (i == num_fields - 4 && !g_str_equal(reply[CONTACTS_ID_COL],
TRACKER_DEFAULT_CONTACT_ME))
return;
if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) {
data->index++;
g_free(temp_id);
temp_id = g_strdup(reply[CONTACTS_ID_COL]);
}
last_index = params->liststartoffset + params->maxlistcount;
if ((data->index <= params->liststartoffset ||
data->index > last_index) &&
params->maxlistcount > 0)
return;
add_entry:
contact = g_new0(struct phonebook_contact, 1);
contact_init(contact, reply);
add_numbers:
contact_add_numbers(contact, reply);
contact_add_emails(contact, reply);
contact_add_addresses(contact, reply);
contact_add_organization(contact, reply);
DBG("contact %p", contact);
/* Adding contacts data to wrapper struct - this data will be used to
* generate vcard list */
if (!cdata_present) {
contact_data = g_new0(struct contact_data, 1);
contact_data->contact = contact;
contact_data->id = g_strdup(reply[CONTACTS_ID_COL]);
data->contacts = g_slist_append(data->contacts, contact_data);
}
return;
done:
vcards = gen_vcards(data->contacts, params);
if (num_fields == 0)
data->cb(vcards->str, vcards->len,
g_slist_length(data->contacts), 0,
data->user_data);
g_string_free(vcards, TRUE);
fail:
g_slist_free(data->contacts);
g_free(temp_id);
temp_id = NULL;
/*
* phonebook_data is freed in query_free_data after call is unreffed.
* It is accessible by pointer from data (pending) associated to call.
* Useful in cases when call was terminated.
*/
}
static void add_to_cache(char **reply, int num_fields, void *user_data)
{
struct phonebook_data *data = user_data;
char *formatted;
int i;
if (reply == NULL || num_fields < 0)
goto done;
/* the first element is the URI, always not empty */
for (i = 1; i < num_fields; i++) {
if (reply[i][0] != '\0')
break;
}
if (i == num_fields &&
!g_str_equal(reply[0], TRACKER_DEFAULT_CONTACT_ME))
return;
if (i == 6)
formatted = g_strdup(reply[6]);
else
formatted = g_strdup_printf("%s;%s;%s;%s;%s",
reply[1], reply[2], reply[3], reply[4],
reply[5]);
/* The owner vCard must have the 0 handle */
if (strcmp(reply[0], TRACKER_DEFAULT_CONTACT_ME) == 0)
data->entry_cb(reply[0], 0, formatted, "",
reply[6], data->user_data);
else
data->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted,
"", reply[6], data->user_data);
g_free(formatted);
return;
done:
if (num_fields <= 0)
data->ready_cb(data->user_data);
/*
* data is freed in query_free_data after call is unreffed.
* It is accessible by pointer from data (pending) associated to call.
* Useful in cases when call was terminated.
*/
}
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 *tmp1, *tmp2, *base, *path = NULL;
gboolean root, child;
int ret = 0;
int len;
root = (g_strcmp0("/", current_folder) == 0);
child = (new_folder && strlen(new_folder) != 0);
switch (flags) {
case 0x02:
/* Go back to root */
if (!child) {
path = g_strdup("/");
goto done;
}
path = g_build_filename(current_folder, new_folder, NULL);
break;
case 0x03:
/* Go up 1 level */
if (root) {
/* Already root */
path = g_strdup("/");
goto done;
}
/*
* Removing one level of the current folder. Current folder
* contains AT LEAST one level since it is not at root folder.
* Use glib utility functions to handle invalid chars in the
* folder path properly.
*/
tmp1 = g_path_get_basename(current_folder);
tmp2 = g_strrstr(current_folder, tmp1);
len = tmp2 - (current_folder + 1);
g_free(tmp1);
if (len == 0)
base = g_strdup("/");
else
base = g_strndup(current_folder, len);
/* Return: one level only */
if (!child) {
path = base;
goto done;
}
path = g_build_filename(base, new_folder, NULL);
g_free(base);
break;
default:
ret = -EBADR;
break;
}
done:
if (path && !folder_is_valid(path))
ret = -ENOENT;
if (ret < 0) {
g_free(path);
path = NULL;
}
if (err)
*err = ret;
return path;
}
void phonebook_req_finalize(void *request)
{
struct DBusPendingCall *call = request;
DBG("");
if (!dbus_pending_call_get_completed(call))
dbus_pending_call_cancel(call);
dbus_pending_call_unref(call);
}
void *phonebook_pull(const char *name, const struct apparam_field *params,
phonebook_cb cb, void *user_data, int *err)
{
struct phonebook_data *data;
const char *query;
reply_list_foreach_t pull_cb;
int col_amount;
DBG("name %s", name);
if (params->maxlistcount == 0) {
query = name2count_query(name);
col_amount = COUNT_QUERY_COL_AMOUNT;
pull_cb = pull_contacts_size;
} else {
query = name2query(name);
col_amount = PULL_QUERY_COL_AMOUNT;
pull_cb = pull_contacts;
}
if (query == NULL) {
if (err)
*err = -ENOENT;
return NULL;
}
data = g_new0(struct phonebook_data, 1);
data->params = params;
data->user_data = user_data;
data->cb = cb;
return query_tracker(query, col_amount, pull_cb, data, g_free, err);
}
void *phonebook_get_entry(const char *folder, const char *id,
const struct apparam_field *params,
phonebook_cb cb, void *user_data, int *err)
{
struct phonebook_data *data;
char *query;
DBusPendingCall *call;
DBG("folder %s id %s", folder, id);
data = g_new0(struct phonebook_data, 1);
data->user_data = user_data;
data->params = params;
data->cb = cb;
data->vcardentry = TRUE;
if (strncmp(id, CONTACT_ID_PREFIX, strlen(CONTACT_ID_PREFIX)) == 0)
query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id,
id, id, id, id, id, id, id, id,
id, id, id, id, id, id);
else
query = g_strdup_printf(CONTACTS_OTHER_QUERY_FROM_URI,
id, id, id);
call = query_tracker(query, PULL_QUERY_COL_AMOUNT, pull_contacts,
data, g_free, err);
g_free(query);
return call;
}
void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
{
struct phonebook_data *data;
const char *query;
DBG("name %s", name);
query = folder2query(name);
if (query == NULL) {
if (err)
*err = -ENOENT;
return NULL;
}
data = g_new0(struct phonebook_data, 1);
data->entry_cb = entry_cb;
data->ready_cb = ready_cb;
data->user_data = user_data;
return query_tracker(query, 7, add_to_cache, data, g_free, err);
}