| /* |
| * 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 |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| #include <libtracker-sparql/tracker-sparql.h> |
| |
| #include "obexd/src/log.h" |
| #include "obexd/src/obex.h" |
| #include "obexd/src/service.h" |
| #include "obexd/src/mimetype.h" |
| #include "phonebook.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 "urn:uuid:" |
| #define CALL_ID_PREFIX "message:" |
| |
| #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 ADDR_DELIM "\37" /* Delimiter used for address data fields */ |
| #define MAX_FIELDS 100 /* Max amount of fields to be concatenated at once*/ |
| #define VCARDS_PART_COUNT 50 /* amount of vcards sent at once to PBAP core */ |
| #define QUERY_OFFSET_FORMAT "%s OFFSET %d" |
| |
| #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), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:country(?aff_addr), \"\"), " \ |
| "\"\31\", rdfs:label(?_role) ), " \ |
| "\"\30\") " \ |
| "WHERE {" \ |
| "?_role nco:hasPostalAddress ?aff_addr" \ |
| "}) " \ |
| "nco:birthDate(?_contact) " \ |
| "(SELECT " \ |
| " ?nick " \ |
| " WHERE { " \ |
| " { " \ |
| " ?_contact nco:nickname ?nick " \ |
| " } UNION { " \ |
| " ?_contact nco:hasAffiliation ?role . " \ |
| " ?role nco:hasIMAddress ?im . " \ |
| " ?im nco:imNickname ?nick " \ |
| " } " \ |
| " } " \ |
| ") " \ |
| "(SELECT GROUP_CONCAT(fn:concat( " \ |
| "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\ |
| "), \"\30\") " \ |
| "WHERE {" \ |
| "?_role nco:url ?url_val . " \ |
| "})" \ |
| "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\"," \ |
| "tracker:coalesce(rdfs:label(?_role), \"\"))," \ |
| "\"\30\") " \ |
| "WHERE { " \ |
| "?_role nco:hasEmailAddress " \ |
| " [ nco:emailAddress ?emailaddress ] " \ |
| "}) " \ |
| "\"NOTACALL\" \"false\" \"false\" " \ |
| "?_contact " \ |
| "WHERE {" \ |
| " ?_contact a nco:PersonContact ." \ |
| " OPTIONAL {?_contact nco:hasAffiliation ?_role .}" \ |
| "}" \ |
| "ORDER BY 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) " \ |
| "(SELECT " \ |
| "?nick " \ |
| "WHERE { " \ |
| "{ " \ |
| "?c nco:nickname ?nick " \ |
| "} UNION { " \ |
| "?c nco:hasAffiliation ?role . " \ |
| "?role nco:hasIMAddress ?im . " \ |
| "?im nco:imNickname ?nick " \ |
| "} " \ |
| "} " \ |
| ") " \ |
| "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 CALLS_CONSTRAINTS(CONSTRAINT) \ |
| " WHERE { " \ |
| "?_call a nmo:Call . " \ |
| "?_unb_contact a nco:Contact . " \ |
| "?_unb_contact nco:hasPhoneNumber ?_cpn . " \ |
| CONSTRAINT \ |
| "OPTIONAL { " \ |
| "{ SELECT ?_contact ?_no ?_role ?_number " \ |
| "count(?_contact) as ?cnt " \ |
| "WHERE { " \ |
| "?_contact a nco:PersonContact . " \ |
| "{ " \ |
| "?_contact nco:hasAffiliation ?_role . "\ |
| "?_role nco:hasPhoneNumber ?_number . " \ |
| "} UNION { " \ |
| "?_contact nco:hasPhoneNumber ?_number" \ |
| "} " \ |
| "?_number maemo:localPhoneNumber ?_no . " \ |
| "} GROUP BY ?_no } " \ |
| "FILTER(?cnt = 1) " \ |
| "?_cpn maemo:localPhoneNumber ?_no . " \ |
| "} " \ |
| "} " |
| |
| #define CALLS_LIST(CONSTRAINT) \ |
| "SELECT ?_call nco:nameFamily(?_contact) " \ |
| "nco:nameGiven(?_contact) nco:nameAdditional(?_contact) " \ |
| "nco:nameHonorificPrefix(?_contact) " \ |
| "nco:nameHonorificSuffix(?_contact) " \ |
| "(SELECT " \ |
| "?nick " \ |
| "WHERE { " \ |
| "{ " \ |
| "?_contact nco:nickname ?nick " \ |
| "} UNION { " \ |
| "?_contact nco:hasAffiliation ?role . " \ |
| "?role nco:hasIMAddress ?im . " \ |
| "?im nco:imNickname ?nick " \ |
| "} " \ |
| "} " \ |
| ") " \ |
| "nco:phoneNumber(?_cpn) " \ |
| CALLS_CONSTRAINTS(CONSTRAINT) \ |
| "ORDER BY DESC(nmo:sentDate(?_call)) " |
| |
| #define CALLS_QUERY(CONSTRAINT) \ |
| "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 . " \ |
| " FILTER (!bound(?_role)) " \ |
| "}" \ |
| "} 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), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\","\ |
| "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\","\ |
| "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ |
| "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) " \ |
| "(SELECT " \ |
| "?nick " \ |
| "WHERE { " \ |
| " { " \ |
| " ?_contact nco:nickname ?nick " \ |
| " } UNION { " \ |
| " ?_contact nco:hasAffiliation ?role . " \ |
| " ?role nco:hasIMAddress ?im . " \ |
| " ?im nco:imNickname ?nick " \ |
| " } " \ |
| " } " \ |
| ") " \ |
| "(SELECT GROUP_CONCAT(fn:concat(?url_value, \"\31\", " \ |
| "tracker:coalesce(rdfs:label(?c_role), \"\")), \"\30\") " \ |
| "WHERE {" \ |
| "?_contact nco:hasAffiliation ?c_role . " \ |
| "?c_role nco:url ?url_value . " \ |
| "})" \ |
| "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\"," \ |
| "tracker:coalesce(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) " \ |
| "?_call " \ |
| CALLS_CONSTRAINTS(CONSTRAINT) \ |
| "ORDER BY DESC(nmo:sentDate(?_call)) " |
| |
| #define MISSED_CONSTRAINT \ |
| "?_call nmo:from ?_unb_contact . " \ |
| "?_call nmo:isSent false . " \ |
| "?_call nmo:isAnswered false . " |
| |
| #define INCOMING_CONSTRAINT \ |
| "?_call nmo:from ?_unb_contact . " \ |
| "?_call nmo:isSent false . " \ |
| "?_call nmo:isAnswered true . " |
| |
| #define OUTGOING_CONSTRAINT \ |
| "?_call nmo:to ?_unb_contact . " \ |
| "?_call nmo:isSent true . " |
| |
| #define COMBINED_CONSTRAINT \ |
| "{ " \ |
| " ?_call nmo:from ?_unb_contact . " \ |
| " ?_call nmo:isSent false " \ |
| "} UNION { " \ |
| " ?_call nmo:to ?_unb_contact . " \ |
| " ?_call nmo:isSent true " \ |
| "} " |
| |
| #define CALL_URI_CONSTRAINT \ |
| COMBINED_CONSTRAINT \ |
| "FILTER (?_call = <%s>) " |
| |
| #define MISSED_CALLS_QUERY CALLS_QUERY(MISSED_CONSTRAINT) |
| #define MISSED_CALLS_LIST CALLS_LIST(MISSED_CONSTRAINT) |
| #define INCOMING_CALLS_QUERY CALLS_QUERY(INCOMING_CONSTRAINT) |
| #define INCOMING_CALLS_LIST CALLS_LIST(INCOMING_CONSTRAINT) |
| #define OUTGOING_CALLS_QUERY CALLS_QUERY(OUTGOING_CONSTRAINT) |
| #define OUTGOING_CALLS_LIST CALLS_LIST(OUTGOING_CONSTRAINT) |
| #define COMBINED_CALLS_QUERY CALLS_QUERY(COMBINED_CONSTRAINT) |
| #define COMBINED_CALLS_LIST CALLS_LIST(COMBINED_CONSTRAINT) |
| #define CONTACT_FROM_CALL_QUERY CALLS_QUERY(CALL_URI_CONSTRAINT) |
| |
| #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), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \ |
| "tracker:coalesce(nco:country(?aff_addr), \"\"), " \ |
| "\"\31\", rdfs:label(?_role) ), " \ |
| "\"\30\") " \ |
| "WHERE {" \ |
| "?_role nco:hasPostalAddress ?aff_addr" \ |
| "}) " \ |
| "nco:birthDate(<%s>) " \ |
| "(SELECT " \ |
| " ?nick " \ |
| " WHERE { " \ |
| " { " \ |
| " ?_contact nco:nickname ?nick " \ |
| " } UNION { " \ |
| " ?_contact nco:hasAffiliation ?role . " \ |
| " ?role nco:hasIMAddress ?im . " \ |
| " ?im nco:imNickname ?nick " \ |
| " } " \ |
| " FILTER (?_contact = <%s>)" \ |
| " } " \ |
| ") " \ |
| "(SELECT GROUP_CONCAT(fn:concat( " \ |
| "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\ |
| "), \"\30\") " \ |
| "WHERE {" \ |
| "?_role nco:url ?url_val . " \ |
| "})" \ |
| "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\"," \ |
| "tracker:coalesce(rdfs:label(?_role), \"\"))," \ |
| "\"\30\") " \ |
| "WHERE { " \ |
| "?_role nco:hasEmailAddress " \ |
| " [ nco:emailAddress ?emailaddress ] " \ |
| "}) " \ |
| "\"NOTACALL\" \"false\" \"false\" " \ |
| "<%s> " \ |
| "WHERE {" \ |
| " <%s> a nco:PersonContact ." \ |
| " OPTIONAL {<%s> nco:hasAffiliation ?_role .}" \ |
| "}" |
| |
| #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 ." \ |
| "}" |
| |
| #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_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 ;" \ |
| "nmo:isRead false ." \ |
| "}" |
| |
| typedef int (*reply_list_foreach_t) (const char **reply, int num_fields, |
| void *user_data); |
| |
| typedef void (*add_field_t) (struct phonebook_contact *contact, |
| const char *value, int type); |
| |
| 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; |
| GCancellable *query_canc; |
| char *req_name; |
| int vcard_part_count; |
| int tracker_index; |
| }; |
| |
| struct phonebook_index { |
| GArray *phonebook; |
| int index; |
| }; |
| |
| static TrackerSparqlConnection *connection = NULL; |
| |
| static const char *name2query(const char *name) |
| { |
| if (g_str_equal(name, PB_CONTACTS)) |
| return CONTACTS_QUERY_ALL; |
| else if (g_str_equal(name, PB_CALLS_INCOMING)) |
| return INCOMING_CALLS_QUERY; |
| else if (g_str_equal(name, PB_CALLS_OUTGOING)) |
| return OUTGOING_CALLS_QUERY; |
| else if (g_str_equal(name, PB_CALLS_MISSED)) |
| return MISSED_CALLS_QUERY; |
| else if (g_str_equal(name, PB_CALLS_COMBINED)) |
| return COMBINED_CALLS_QUERY; |
| |
| return NULL; |
| } |
| |
| static const char *name2count_query(const char *name) |
| { |
| if (g_str_equal(name, PB_CONTACTS)) |
| return CONTACTS_COUNT_QUERY; |
| else if (g_str_equal(name, PB_CALLS_INCOMING)) |
| return INCOMING_CALLS_COUNT_QUERY; |
| else if (g_str_equal(name, PB_CALLS_OUTGOING)) |
| return OUTGOING_CALLS_COUNT_QUERY; |
| else if (g_str_equal(name, PB_CALLS_MISSED)) |
| return MISSED_CALLS_COUNT_QUERY; |
| else if (g_str_equal(name, PB_CALLS_COMBINED)) |
| 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, PB_TELECOM_FOLDER)) |
| return TRUE; |
| else if (g_str_equal(folder, PB_CONTACTS_FOLDER)) |
| return TRUE; |
| else if (g_str_equal(folder, PB_CALLS_INCOMING_FOLDER)) |
| return TRUE; |
| else if (g_str_equal(folder, PB_CALLS_OUTGOING_FOLDER)) |
| return TRUE; |
| else if (g_str_equal(folder, PB_CALLS_MISSED_FOLDER)) |
| return TRUE; |
| else if (g_str_equal(folder, PB_CALLS_COMBINED_FOLDER)) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| static const char *folder2query(const char *folder) |
| { |
| if (g_str_equal(folder, PB_CONTACTS_FOLDER)) |
| return CONTACTS_QUERY_ALL_LIST; |
| else if (g_str_equal(folder, PB_CALLS_INCOMING_FOLDER)) |
| return INCOMING_CALLS_LIST; |
| else if (g_str_equal(folder, PB_CALLS_OUTGOING_FOLDER)) |
| return OUTGOING_CALLS_LIST; |
| else if (g_str_equal(folder, PB_CALLS_MISSED_FOLDER)) |
| return MISSED_CALLS_LIST; |
| else if (g_str_equal(folder, PB_CALLS_COMBINED_FOLDER)) |
| return COMBINED_CALLS_LIST; |
| |
| return NULL; |
| } |
| |
| static const char **string_array_from_cursor(TrackerSparqlCursor *cursor, |
| int array_len) |
| { |
| const char **result; |
| int i; |
| |
| result = g_new0(const char *, array_len); |
| |
| for (i = 0; i < array_len; ++i) { |
| TrackerSparqlValueType type; |
| |
| type = tracker_sparql_cursor_get_value_type(cursor, i); |
| |
| if (type == TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE || |
| type == TRACKER_SPARQL_VALUE_TYPE_UNBOUND) |
| /* For null/unbound type filling result part with ""*/ |
| result[i] = ""; |
| else |
| /* Filling with string representation of content*/ |
| result[i] = tracker_sparql_cursor_get_string(cursor, i, |
| NULL); |
| } |
| |
| return result; |
| } |
| |
| static void update_cancellable(struct phonebook_data *pdata, |
| GCancellable *canc) |
| { |
| if (pdata->query_canc) |
| g_object_unref(pdata->query_canc); |
| |
| pdata->query_canc = canc; |
| } |
| |
| static void async_query_cursor_next_cb(GObject *source, GAsyncResult *result, |
| gpointer user_data) |
| { |
| struct pending_reply *pending = user_data; |
| TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR(source); |
| GCancellable *cancellable; |
| GError *error = NULL; |
| gboolean success; |
| const char **node; |
| int err; |
| |
| success = tracker_sparql_cursor_next_finish( |
| TRACKER_SPARQL_CURSOR(source), |
| result, &error); |
| |
| if (!success) { |
| if (error) { |
| DBG("cursor_next error: %s", error->message); |
| g_error_free(error); |
| } else |
| /* When tracker_sparql_cursor_next_finish ends with |
| * failure and no error is set, that means end of |
| * results returned by query */ |
| pending->callback(NULL, 0, pending->user_data); |
| |
| goto failed; |
| } |
| |
| node = string_array_from_cursor(cursor, pending->num_fields); |
| err = pending->callback(node, pending->num_fields, pending->user_data); |
| g_free(node); |
| |
| /* Fetch next result only if processing current chunk ended with |
| * success. Sometimes during processing data, we are able to determine |
| * if there is no need to get more data from tracker - by example |
| * stored amount of data parts is big enough for sending and we might |
| * want to suspend processing or just some error occurred. */ |
| if (!err) { |
| cancellable = g_cancellable_new(); |
| update_cancellable(pending->user_data, cancellable); |
| tracker_sparql_cursor_next_async(cursor, cancellable, |
| async_query_cursor_next_cb, |
| pending); |
| return; |
| } |
| |
| failed: |
| g_object_unref(cursor); |
| g_free(pending); |
| } |
| |
| static int query_tracker(const char *query, int num_fields, |
| reply_list_foreach_t callback, void *user_data) |
| { |
| struct pending_reply *pending; |
| GCancellable *cancellable; |
| TrackerSparqlCursor *cursor; |
| GError *error = NULL; |
| |
| DBG(""); |
| |
| if (connection == NULL) |
| connection = tracker_sparql_connection_get_direct( |
| NULL, &error); |
| |
| if (!connection) { |
| if (error) { |
| DBG("direct-connection error: %s", error->message); |
| g_error_free(error); |
| } |
| |
| return -EINTR; |
| } |
| |
| cancellable = g_cancellable_new(); |
| update_cancellable(user_data, cancellable); |
| cursor = tracker_sparql_connection_query(connection, query, |
| cancellable, &error); |
| |
| if (cursor == NULL) { |
| if (error) { |
| DBG("connection_query error: %s", error->message); |
| g_error_free(error); |
| } |
| |
| g_object_unref(cancellable); |
| |
| return -EINTR; |
| } |
| |
| pending = g_new0(struct pending_reply, 1); |
| pending->callback = callback; |
| pending->user_data = user_data; |
| pending->num_fields = num_fields; |
| |
| /* Now asynchronously going through each row of results - callback |
| * async_query_cursor_next_cb will be called ALWAYS, even if async |
| * request was canceled */ |
| tracker_sparql_cursor_next_async(cursor, cancellable, |
| async_query_cursor_next_cb, |
| pending); |
| |
| return 0; |
| } |
| |
| static char *iso8601_utc_to_localtime(const char *datetime) |
| { |
| time_t time; |
| struct tm tm, *local; |
| char localdate[32]; |
| int nr; |
| |
| memset(&tm, 0, sizeof(tm)); |
| |
| nr = sscanf(datetime, "%04u-%02u-%02uT%02u:%02u:%02u", |
| &tm.tm_year, &tm.tm_mon, &tm.tm_mday, |
| &tm.tm_hour, &tm.tm_min, &tm.tm_sec); |
| if (nr < 6) { |
| /* Invalid time format */ |
| error("sscanf(): %s (%d)", strerror(errno), errno); |
| return g_strdup(""); |
| } |
| |
| /* Time already in localtime */ |
| if (!g_str_has_suffix(datetime, "Z")) { |
| 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 gboolean contact_matches(struct contact_data *c_data, const char *id, |
| const char *datetime) |
| { |
| char *localtime; |
| int cmp_ret; |
| |
| if (g_strcmp0(c_data->id, id) != 0) |
| return FALSE; |
| |
| /* id is equal and not call history entry => contact matches */ |
| if (c_data->contact->calltype == CALL_TYPE_NOT_A_CALL) |
| return TRUE; |
| |
| /* for call history entries have to compare also timestamps of calls */ |
| localtime = iso8601_utc_to_localtime(datetime); |
| cmp_ret = g_strcmp0(c_data->contact->datetime, localtime); |
| g_free(localtime); |
| |
| return (cmp_ret == 0) ? TRUE : FALSE; |
| } |
| |
| static struct phonebook_contact *find_contact(GSList *contacts, const char *id, |
| const char *datetime) |
| { |
| GSList *l; |
| |
| for (l = contacts; l; l = l->next) { |
| struct contact_data *c_data = l->data; |
| |
| if (contact_matches(c_data, id, datetime)) |
| 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 gboolean addr_matches(struct phonebook_addr *a, struct phonebook_addr *b) |
| { |
| GSList *la, *lb; |
| |
| if (a->type != b->type) |
| return FALSE; |
| |
| for (la = a->fields, lb = b->fields; la && lb; |
| la = la->next, lb = lb->next) { |
| char *field_a = la->data; |
| char *field_b = lb->data; |
| |
| if (g_strcmp0(field_a, field_b) != 0) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /* generates phonebook_addr struct from tracker address data string. */ |
| static struct phonebook_addr *gen_addr(const char *address, int type) |
| { |
| struct phonebook_addr *addr; |
| GSList *fields = NULL; |
| char **addr_parts; |
| int i; |
| |
| /* This test handles cases when address points to empty string |
| * (or address is NULL pointer) or string containing only six |
| * separators. It indicates that none of address fields is present |
| * and there is no sense to create dummy phonebook_addr struct */ |
| if (address == NULL || strlen(address) < ADDR_FIELD_AMOUNT) |
| return NULL; |
| |
| addr_parts = g_strsplit(address, ADDR_DELIM, ADDR_FIELD_AMOUNT); |
| |
| for (i = 0; i < ADDR_FIELD_AMOUNT; ++i) |
| fields = g_slist_append(fields, g_strdup(addr_parts[i])); |
| |
| g_strfreev(addr_parts); |
| |
| addr = g_new0(struct phonebook_addr, 1); |
| addr->fields = fields; |
| addr->type = type; |
| |
| return addr; |
| } |
| |
| static void add_address(struct phonebook_contact *contact, |
| const char *address, int type) |
| { |
| struct phonebook_addr *addr; |
| GSList *l; |
| |
| addr = gen_addr(address, type); |
| if (addr == NULL) |
| return; |
| |
| /* Not adding address if there is already added with the same value. |
| * These type of checks have to be done because sometimes tracker |
| * returns results for contact data in more than 1 row - then the same |
| * address may be returned more than once in query results */ |
| for (l = contact->addresses; l; l = l->next) { |
| struct phonebook_addr *tmp = l->data; |
| |
| if (addr_matches(tmp, addr)) { |
| phonebook_addr_free(addr); |
| return; |
| } |
| } |
| |
| contact->addresses = g_slist_append(contact->addresses, addr); |
| } |
| |
| static void add_url(struct phonebook_contact *contact, const char *url_val, |
| int type) |
| { |
| struct phonebook_field *url; |
| |
| if (url_val == NULL || strlen(url_val) == 0) |
| return; |
| |
| /* Not adding url if there is already added with the same value */ |
| if (find_field(contact->urls, url_val, type)) |
| return; |
| |
| url = g_new0(struct phonebook_field, 1); |
| |
| url->text = g_strdup(url_val); |
| url->type = type; |
| |
| contact->urls = g_slist_append(contact->urls, url); |
| } |
| |
| 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); |
| } |
| |
| return vcards; |
| } |
| |
| static int pull_contacts_size(const 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, TRUE, data->user_data); |
| return -EINTR; |
| } |
| |
| if (reply != NULL) { |
| data->index = atoi(reply[0]); |
| return 0; |
| } |
| |
| data->cb(NULL, 0, data->index, data->newmissedcalls, TRUE, |
| data->user_data); |
| |
| return 0; |
| /* |
| * phonebook_data is freed in phonebook_req_finalize. Useful in |
| * cases when call is 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, |
| const char **reply) |
| { |
| if (reply[COL_FAMILY_NAME][0] == '\0' && |
| reply[COL_GIVEN_NAME][0] == '\0' && |
| reply[COL_ADDITIONAL_NAME][0] == '\0' && |
| reply[COL_NAME_PREFIX][0] == '\0' && |
| reply[COL_NAME_SUFFIX][0] == '\0') { |
| if (reply[COL_FULL_NAME][0] != '\0') |
| contact->family = g_strdup(reply[COL_FULL_NAME]); |
| else |
| contact->family = g_strdup(reply[COL_NICKNAME]); |
| } else { |
| 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->fullname = g_strdup(reply[COL_FULL_NAME]); |
| contact->birthday = g_strdup(reply[COL_BIRTH_DATE]); |
| contact->nickname = g_strdup(reply[COL_NICKNAME]); |
| 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_aff_number(struct phonebook_contact *contact, |
| const char *pnumber, const char *aff_type) |
| { |
| 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 |
| /* if this is no fax/mobile phone, then adding phone number |
| * type based on type of the affilation field */ |
| add_phone_number(contact, number, get_phone_type(aff_type)); |
| |
| failed: |
| g_strfreev(num_parts); |
| } |
| |
| static void contact_add_numbers(struct phonebook_contact *contact, |
| const char **reply) |
| { |
| char **aff_numbers; |
| int i; |
| |
| /* Filling phone 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_aff_number(contact, aff_numbers[i], |
| reply[COL_AFF_TYPE]); |
| |
| g_strfreev(aff_numbers); |
| } |
| |
| static enum phonebook_field_type get_field_type(const char *affilation) |
| { |
| if (g_strcmp0(AFFILATION_HOME, affilation) == 0) |
| return FIELD_TYPE_HOME; |
| else if (g_strcmp0(AFFILATION_WORK, affilation) == 0) |
| return FIELD_TYPE_WORK; |
| |
| return FIELD_TYPE_OTHER; |
| } |
| |
| static void add_aff_field(struct phonebook_contact *contact, |
| const char *aff_email, add_field_t add_field_cb) |
| { |
| char **email_parts; |
| char *type, *email; |
| |
| /* Emails from affilation data, are represented as real email |
| * string and affilation type - those strings are separated by |
| * SUB_DELIM string */ |
| email_parts = g_strsplit(aff_email, SUB_DELIM, 2); |
| |
| if (!email_parts) |
| return; |
| |
| if (email_parts[0]) |
| email = email_parts[0]; |
| else |
| goto failed; |
| |
| if (email_parts[1]) |
| type = email_parts[1]; |
| else |
| goto failed; |
| |
| add_field_cb(contact, email, get_field_type(type)); |
| |
| failed: |
| g_strfreev(email_parts); |
| } |
| |
| static void contact_add_emails(struct phonebook_contact *contact, |
| const char **reply) |
| { |
| char **aff_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_aff_field(contact, aff_emails[i], add_email); |
| |
| g_strfreev(aff_emails); |
| } |
| |
| static void contact_add_addresses(struct phonebook_contact *contact, |
| const char **reply) |
| { |
| char **aff_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_aff_field(contact, aff_addr[i], add_address); |
| |
| g_strfreev(aff_addr); |
| } |
| |
| static void contact_add_urls(struct phonebook_contact *contact, |
| const char **reply) |
| { |
| char **aff_url; |
| int i; |
| |
| /* Addresses from affilation */ |
| aff_url = g_strsplit(reply[COL_URL], MAIN_DELIM, MAX_FIELDS); |
| |
| if (aff_url) |
| for (i = 0; aff_url[i] != NULL; ++i) |
| add_aff_field(contact, aff_url[i], add_url); |
| |
| g_strfreev(aff_url); |
| } |
| |
| static void contact_add_organization(struct phonebook_contact *contact, |
| const 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 free_data_contacts(struct phonebook_data *data) |
| { |
| GSList *l; |
| |
| /* freeing contacts */ |
| for (l = data->contacts; l; l = l->next) { |
| struct contact_data *c_data = l->data; |
| |
| g_free(c_data->id); |
| phonebook_contact_free(c_data->contact); |
| g_free(c_data); |
| } |
| |
| g_slist_free(data->contacts); |
| data->contacts = NULL; |
| } |
| |
| static void send_pull_part(struct phonebook_data *data, |
| const struct apparam_field *params, gboolean lastpart) |
| { |
| GString *vcards; |
| |
| DBG(""); |
| vcards = gen_vcards(data->contacts, params); |
| data->cb(vcards->str, vcards->len, g_slist_length(data->contacts), |
| data->newmissedcalls, lastpart, data->user_data); |
| |
| if (!lastpart) |
| free_data_contacts(data); |
| g_string_free(vcards, TRUE); |
| } |
| |
| static int pull_contacts(const 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; |
| int last_index, i; |
| gboolean cdata_present = FALSE, part_sent = FALSE; |
| static char *temp_id = NULL; |
| |
| if (num_fields < 0) { |
| data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data); |
| goto fail; |
| } |
| |
| DBG("reply %p", reply); |
| data->tracker_index++; |
| |
| 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], |
| reply[COL_DATE]); |
| |
| /* 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 0; |
| |
| if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) { |
| data->index++; |
| g_free(temp_id); |
| temp_id = g_strdup(reply[CONTACTS_ID_COL]); |
| |
| /* Incrementing counter for vcards in current part of data, |
| * but only if liststartoffset has been already reached */ |
| if (data->index > params->liststartoffset) |
| data->vcard_part_count++; |
| } |
| |
| if (data->vcard_part_count > VCARDS_PART_COUNT) { |
| DBG("Part of vcard data ready for sending..."); |
| data->vcard_part_count = 0; |
| /* Sending part of data to PBAP core - more data can be still |
| * fetched, so marking lastpart as FALSE */ |
| send_pull_part(data, params, FALSE); |
| |
| /* Later, after adding contact data, need to return -EINTR to |
| * stop fetching more data for this request. Data will be |
| * downloaded again from this point, when phonebook_pull_read |
| * will be called again with current request as a parameter*/ |
| part_sent = TRUE; |
| } |
| |
| last_index = params->liststartoffset + params->maxlistcount; |
| |
| if (data->index <= params->liststartoffset) |
| return 0; |
| |
| /* max number of results achieved - need send vcards data that was |
| * already collected and stop further data processing (these operations |
| * will be invoked in "done" section) */ |
| if (data->index > last_index && params->maxlistcount > 0) { |
| DBG("Maxlistcount achieved"); |
| goto done; |
| } |
| |
| 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_urls(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); |
| } |
| |
| if (part_sent) |
| return -EINTR; |
| |
| return 0; |
| |
| done: |
| /* Processing is end, this is definitely last part of transmission |
| * (marking lastpart as TRUE) */ |
| send_pull_part(data, params, TRUE); |
| |
| fail: |
| g_free(temp_id); |
| temp_id = NULL; |
| |
| return -EINTR; |
| /* |
| * phonebook_data is freed in phonebook_req_finalize. Useful in |
| * cases when call is terminated. |
| */ |
| } |
| |
| static int add_to_cache(const 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 0; |
| |
| if (i == 7) |
| formatted = g_strdup(reply[7]); |
| else 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 0; |
| |
| done: |
| if (num_fields <= 0) |
| data->ready_cb(data->user_data); |
| |
| return -EINTR; |
| /* |
| * phonebook_data is freed in phonebook_req_finalize. Useful in |
| * cases when call is terminated. |
| */ |
| } |
| |
| int phonebook_init(void) |
| { |
| g_thread_init(NULL); |
| g_type_init(); |
| |
| 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; |
| } |
| |
| static int pull_newmissedcalls(const char **reply, int num_fields, |
| void *user_data) |
| { |
| struct phonebook_data *data = user_data; |
| reply_list_foreach_t pull_cb; |
| int col_amount, err; |
| const char *query; |
| int nmissed; |
| |
| if (num_fields < 0) { |
| data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data); |
| |
| return -EINTR; |
| } |
| |
| if (reply != NULL) { |
| nmissed = atoi(reply[0]); |
| data->newmissedcalls = |
| nmissed <= UINT8_MAX ? nmissed : UINT8_MAX; |
| DBG("newmissedcalls %d", data->newmissedcalls); |
| |
| return 0; |
| } |
| |
| if (data->params->maxlistcount == 0) { |
| query = name2count_query(PB_CALLS_MISSED); |
| col_amount = COUNT_QUERY_COL_AMOUNT; |
| pull_cb = pull_contacts_size; |
| } else { |
| query = name2query(PB_CALLS_MISSED); |
| col_amount = PULL_QUERY_COL_AMOUNT; |
| pull_cb = pull_contacts; |
| } |
| |
| err = query_tracker(query, col_amount, pull_cb, data); |
| if (err < 0) { |
| data->cb(NULL, 0, err, 0, TRUE, data->user_data); |
| |
| return -EINTR; |
| } |
| |
| return 0; |
| } |
| |
| void phonebook_req_finalize(void *request) |
| { |
| struct phonebook_data *data = request; |
| |
| DBG(""); |
| |
| if (!data) |
| return; |
| |
| /* canceling asynchronous operation on tracker if any is active */ |
| if (data->query_canc) { |
| g_cancellable_cancel(data->query_canc); |
| g_object_unref(data->query_canc); |
| } |
| |
| free_data_contacts(data); |
| g_free(data->req_name); |
| g_free(data); |
| } |
| |
| void *phonebook_pull(const char *name, const struct apparam_field *params, |
| phonebook_cb cb, void *user_data, int *err) |
| { |
| struct phonebook_data *data; |
| |
| DBG("name %s", name); |
| |
| data = g_new0(struct phonebook_data, 1); |
| data->params = params; |
| data->user_data = user_data; |
| data->cb = cb; |
| data->req_name = g_strdup(name); |
| |
| if (err) |
| *err = 0; |
| |
| return data; |
| } |
| |
| int phonebook_pull_read(void *request) |
| { |
| struct phonebook_data *data = request; |
| reply_list_foreach_t pull_cb; |
| const char *query; |
| char *offset_query; |
| int col_amount; |
| int ret; |
| |
| if (!data) |
| return -ENOENT; |
| |
| data->newmissedcalls = 0; |
| |
| if (g_strcmp0(data->req_name, PB_CALLS_MISSED) == 0 && |
| data->tracker_index == 0) { |
| /* new missed calls amount should be counted only once - it |
| * will be done during generating first part of results of |
| * missed calls history */ |
| query = NEW_MISSED_CALLS_COUNT_QUERY; |
| col_amount = COUNT_QUERY_COL_AMOUNT; |
| pull_cb = pull_newmissedcalls; |
| } else if (data->params->maxlistcount == 0) { |
| query = name2count_query(data->req_name); |
| col_amount = COUNT_QUERY_COL_AMOUNT; |
| pull_cb = pull_contacts_size; |
| } else { |
| query = name2query(data->req_name); |
| col_amount = PULL_QUERY_COL_AMOUNT; |
| pull_cb = pull_contacts; |
| } |
| |
| if (query == NULL) |
| return -ENOENT; |
| |
| if (pull_cb == pull_contacts && data->tracker_index > 0) { |
| /* Adding offset to pull query to download next parts of data |
| * from tracker (phonebook_pull_read may be called many times |
| * from PBAP core to fetch data partially) */ |
| offset_query = g_strdup_printf(QUERY_OFFSET_FORMAT, query, |
| data->tracker_index); |
| ret = query_tracker(offset_query, col_amount, pull_cb, data); |
| |
| g_free(offset_query); |
| |
| return ret; |
| } |
| |
| return query_tracker(query, col_amount, pull_cb, data); |
| } |
| |
| 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; |
| int ret; |
| |
| 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 (g_str_has_prefix(id, CONTACT_ID_PREFIX) == TRUE || |
| g_strcmp0(id, TRACKER_DEFAULT_CONTACT_ME) == 0) |
| query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id, |
| id, id, id, id, id, id, id, id, id); |
| else if (g_str_has_prefix(id, CALL_ID_PREFIX) == TRUE) |
| query = g_strdup_printf(CONTACT_FROM_CALL_QUERY, id); |
| else |
| query = g_strdup_printf(CONTACTS_OTHER_QUERY_FROM_URI, |
| id, id, id); |
| |
| ret = query_tracker(query, PULL_QUERY_COL_AMOUNT, pull_contacts, data); |
| if (err) |
| *err = ret; |
| |
| g_free(query); |
| |
| return data; |
| } |
| |
| void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, |
| phonebook_cache_ready_cb ready_cb, void *user_data, int *err) |
| { |
| struct phonebook_data *data; |
| const char *query; |
| int ret; |
| |
| 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; |
| |
| ret = query_tracker(query, 8, add_to_cache, data); |
| if (err) |
| *err = ret; |
| |
| return data; |
| } |