2 * Phonebook access through D-Bus vCard and call history service
4 * Copyright (C) 2010 Nokia Corporation
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 #include <dbus/dbus.h>
30 #include <libtracker-sparql/tracker-sparql.h>
36 #include "phonebook.h"
39 #define TRACKER_SERVICE "org.freedesktop.Tracker1"
40 #define TRACKER_RESOURCES_PATH "/org/freedesktop/Tracker1/Resources"
41 #define TRACKER_RESOURCES_INTERFACE "org.freedesktop.Tracker1.Resources"
43 #define TRACKER_DEFAULT_CONTACT_ME "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#default-contact-me"
44 #define AFFILATION_HOME "Home"
45 #define AFFILATION_WORK "Work"
46 #define ADDR_FIELD_AMOUNT 7
47 #define PULL_QUERY_COL_AMOUNT 23
48 #define COUNT_QUERY_COL_AMOUNT 1
50 #define COL_PHONE_AFF 0 /* work/home phone numbers */
51 #define COL_FULL_NAME 1
52 #define COL_FAMILY_NAME 2
53 #define COL_GIVEN_NAME 3
54 #define COL_ADDITIONAL_NAME 4
55 #define COL_NAME_PREFIX 5
56 #define COL_NAME_SUFFIX 6
57 #define COL_ADDR_AFF 7 /* addresses from affilation */
58 #define COL_BIRTH_DATE 8
59 #define COL_NICKNAME 9
62 #define COL_ORG_ROLE 12
65 #define COL_AFF_TYPE 15
66 #define COL_ORG_NAME 16
67 #define COL_ORG_DEPARTMENT 17
68 #define COL_EMAIL_AFF 18 /* email's from affilation (work/home) */
71 #define COL_ANSWERED 21
72 #define CONTACTS_ID_COL 22
73 #define CONTACT_ID_PREFIX "urn:uuid:"
74 #define CALL_ID_PREFIX "message:"
76 #define FAX_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#FaxNumber"
77 #define MOBILE_NUM_TYPE "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#CellPhoneNumber"
79 #define MAIN_DELIM "\30" /* Main delimiter between phones, addresses, emails*/
80 #define SUB_DELIM "\31" /* Delimiter used in telephone number strings*/
81 #define ADDR_DELIM "\37" /* Delimiter used for address data fields */
82 #define MAX_FIELDS 100 /* Max amount of fields to be concatenated at once*/
83 #define VCARDS_PART_COUNT 50 /* amount of vcards sent at once to PBAP core */
84 #define QUERY_OFFSET_FORMAT "%s OFFSET %d"
86 #define CONTACTS_QUERY_ALL \
88 "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \
89 "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \
91 " ?_role nco:hasPhoneNumber ?aff_number" \
93 "nco:fullname(?_contact) " \
94 "nco:nameFamily(?_contact) " \
95 "nco:nameGiven(?_contact) " \
96 "nco:nameAdditional(?_contact) " \
97 "nco:nameHonorificPrefix(?_contact) " \
98 "nco:nameHonorificSuffix(?_contact) " \
99 "(SELECT GROUP_CONCAT(fn:concat(" \
100 "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \
101 "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \
102 "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \
103 "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \
104 "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \
105 "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \
106 "tracker:coalesce(nco:country(?aff_addr), \"\"), " \
107 "\"\31\", rdfs:label(?_role) ), " \
110 "?_role nco:hasPostalAddress ?aff_addr" \
112 "nco:birthDate(?_contact) " \
117 " ?_contact nco:nickname ?nick " \
119 " ?_contact nco:hasAffiliation ?role . " \
120 " ?role nco:hasIMAddress ?im . " \
121 " ?im nco:imNickname ?nick " \
125 "(SELECT GROUP_CONCAT(fn:concat( " \
126 "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\
129 "?_role nco:url ?url_val . " \
131 "nie:url(nco:photo(?_contact)) " \
132 "nco:role(?_role) " \
133 "nco:contactUID(?_contact) " \
134 "nco:title(?_role) " \
135 "rdfs:label(?_role) " \
136 "nco:fullname(nco:org(?_role))" \
137 "nco:department(?_role) " \
138 "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \
139 "tracker:coalesce(rdfs:label(?_role), \"\"))," \
142 "?_role nco:hasEmailAddress " \
143 " [ nco:emailAddress ?emailaddress ] " \
145 "\"NOTACALL\" \"false\" \"false\" " \
148 " ?_contact a nco:PersonContact ." \
149 " OPTIONAL {?_contact nco:hasAffiliation ?_role .}" \
151 "ORDER BY tracker:id(?_contact)"
153 #define CONTACTS_QUERY_ALL_LIST \
154 "SELECT ?c nco:nameFamily(?c) " \
155 "nco:nameGiven(?c) nco:nameAdditional(?c) " \
156 "nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) " \
161 "?c nco:nickname ?nick " \
163 "?c nco:hasAffiliation ?role . " \
164 "?role nco:hasIMAddress ?im . " \
165 "?im nco:imNickname ?nick " \
169 "nco:phoneNumber(?h) " \
171 "?c a nco:PersonContact . " \
172 "OPTIONAL { ?c nco:hasPhoneNumber ?h . } " \
174 "?c nco:hasAffiliation ?a . " \
175 "?a nco:hasPhoneNumber ?h . " \
179 #define CALLS_CONSTRAINTS(CONSTRAINT) \
181 "?_call a nmo:Call . " \
182 "?_unb_contact a nco:Contact . " \
183 "?_unb_contact nco:hasPhoneNumber ?_cpn . " \
186 "{ SELECT ?_contact ?_no ?_role ?_number " \
187 "count(?_contact) as ?cnt " \
189 "?_contact a nco:PersonContact . " \
191 "?_contact nco:hasAffiliation ?_role . "\
192 "?_role nco:hasPhoneNumber ?_number . " \
194 "?_contact nco:hasPhoneNumber ?_number" \
196 "?_number maemo:localPhoneNumber ?_no . " \
197 "} GROUP BY ?_no } " \
198 "FILTER(?cnt = 1) " \
199 "?_cpn maemo:localPhoneNumber ?_no . " \
203 #define CALLS_LIST(CONSTRAINT) \
204 "SELECT ?_call nco:nameFamily(?_contact) " \
205 "nco:nameGiven(?_contact) nco:nameAdditional(?_contact) " \
206 "nco:nameHonorificPrefix(?_contact) " \
207 "nco:nameHonorificSuffix(?_contact) " \
212 "?_contact nco:nickname ?nick " \
214 "?_contact nco:hasAffiliation ?role . " \
215 "?role nco:hasIMAddress ?im . " \
216 "?im nco:imNickname ?nick " \
220 "nco:phoneNumber(?_cpn) " \
221 CALLS_CONSTRAINTS(CONSTRAINT) \
222 "ORDER BY DESC(nmo:sentDate(?_call)) "
224 #define CALLS_QUERY(CONSTRAINT) \
226 "(SELECT fn:concat(rdf:type(?role_number)," \
227 "\"\31\", nco:phoneNumber(?role_number))" \
230 " ?_role nco:hasPhoneNumber ?role_number " \
231 " FILTER (?role_number = ?_number)" \
233 "?_unb_contact nco:hasPhoneNumber ?role_number . " \
234 " FILTER (!bound(?_role)) " \
236 "} GROUP BY nco:phoneNumber(?role_number) ) " \
237 "nco:fullname(?_contact) " \
238 "nco:nameFamily(?_contact) " \
239 "nco:nameGiven(?_contact) " \
240 "nco:nameAdditional(?_contact) " \
241 "nco:nameHonorificPrefix(?_contact) " \
242 "nco:nameHonorificSuffix(?_contact) " \
243 "(SELECT GROUP_CONCAT(fn:concat(" \
244 "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \
245 "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\","\
246 "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\","\
247 "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \
248 "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \
249 "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \
250 "tracker:coalesce(nco:country(?aff_addr), \"\"), " \
251 "\"\31\", rdfs:label(?c_role) ), " \
254 "?_contact nco:hasAffiliation ?c_role . " \
255 "?c_role nco:hasPostalAddress ?aff_addr" \
257 "nco:birthDate(?_contact) " \
262 " ?_contact nco:nickname ?nick " \
264 " ?_contact nco:hasAffiliation ?role . " \
265 " ?role nco:hasIMAddress ?im . " \
266 " ?im nco:imNickname ?nick " \
270 "(SELECT GROUP_CONCAT(fn:concat(?url_value, \"\31\", " \
271 "tracker:coalesce(rdfs:label(?c_role), \"\")), \"\30\") " \
273 "?_contact nco:hasAffiliation ?c_role . " \
274 "?c_role nco:url ?url_value . " \
276 "nie:url(nco:photo(?_contact)) " \
277 "nco:role(?_role) " \
278 "nco:contactUID(?_contact) " \
279 "nco:title(?_role) " \
280 "rdfs:label(?_role) " \
281 "nco:fullname(nco:org(?_role)) " \
282 "nco:department(?_role) " \
283 "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \
284 "tracker:coalesce(rdfs:label(?c_role), \"\"))," \
287 "?_contact nco:hasAffiliation ?c_role . " \
288 "?c_role nco:hasEmailAddress " \
289 " [ nco:emailAddress ?emailaddress ] " \
291 "nmo:receivedDate(?_call) " \
292 "nmo:isSent(?_call) " \
293 "nmo:isAnswered(?_call) " \
295 CALLS_CONSTRAINTS(CONSTRAINT) \
296 "ORDER BY DESC(nmo:sentDate(?_call)) "
298 #define MISSED_CONSTRAINT \
299 "?_call nmo:from ?_unb_contact . " \
300 "?_call nmo:isSent false . " \
301 "?_call nmo:isAnswered false . "
303 #define INCOMING_CONSTRAINT \
304 "?_call nmo:from ?_unb_contact . " \
305 "?_call nmo:isSent false . " \
306 "?_call nmo:isAnswered true . "
308 #define OUTGOING_CONSTRAINT \
309 "?_call nmo:to ?_unb_contact . " \
310 "?_call nmo:isSent true . "
312 #define COMBINED_CONSTRAINT \
314 " ?_call nmo:from ?_unb_contact . " \
315 " ?_call nmo:isSent false " \
317 " ?_call nmo:to ?_unb_contact . " \
318 " ?_call nmo:isSent true " \
321 #define CALL_URI_CONSTRAINT \
322 COMBINED_CONSTRAINT \
323 "FILTER (?_call = <%s>) "
325 #define MISSED_CALLS_QUERY CALLS_QUERY(MISSED_CONSTRAINT)
326 #define MISSED_CALLS_LIST CALLS_LIST(MISSED_CONSTRAINT)
327 #define INCOMING_CALLS_QUERY CALLS_QUERY(INCOMING_CONSTRAINT)
328 #define INCOMING_CALLS_LIST CALLS_LIST(INCOMING_CONSTRAINT)
329 #define OUTGOING_CALLS_QUERY CALLS_QUERY(OUTGOING_CONSTRAINT)
330 #define OUTGOING_CALLS_LIST CALLS_LIST(OUTGOING_CONSTRAINT)
331 #define COMBINED_CALLS_QUERY CALLS_QUERY(COMBINED_CONSTRAINT)
332 #define COMBINED_CALLS_LIST CALLS_LIST(COMBINED_CONSTRAINT)
333 #define CONTACT_FROM_CALL_QUERY CALLS_QUERY(CALL_URI_CONSTRAINT)
335 #define CONTACTS_QUERY_FROM_URI \
337 "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number)," \
338 "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")" \
340 " ?_role nco:hasPhoneNumber ?aff_number" \
342 "nco:fullname(<%s>) " \
343 "nco:nameFamily(<%s>) " \
344 "nco:nameGiven(<%s>) " \
345 "nco:nameAdditional(<%s>) " \
346 "nco:nameHonorificPrefix(<%s>) " \
347 "nco:nameHonorificSuffix(<%s>) " \
348 "(SELECT GROUP_CONCAT(fn:concat(" \
349 "tracker:coalesce(nco:pobox(?aff_addr), \"\"), \"\37\"," \
350 "tracker:coalesce(nco:extendedAddress(?aff_addr), \"\"), \"\37\"," \
351 "tracker:coalesce(nco:streetAddress(?aff_addr), \"\"), \"\37\"," \
352 "tracker:coalesce(nco:locality(?aff_addr), \"\"), \"\37\"," \
353 "tracker:coalesce(nco:region(?aff_addr), \"\"), \"\37\"," \
354 "tracker:coalesce(nco:postalcode(?aff_addr), \"\"), \"\37\"," \
355 "tracker:coalesce(nco:country(?aff_addr), \"\"), " \
356 "\"\31\", rdfs:label(?_role) ), " \
359 "?_role nco:hasPostalAddress ?aff_addr" \
361 "nco:birthDate(<%s>) " \
366 " ?_contact nco:nickname ?nick " \
368 " ?_contact nco:hasAffiliation ?role . " \
369 " ?role nco:hasIMAddress ?im . " \
370 " ?im nco:imNickname ?nick " \
372 " FILTER (?_contact = <%s>)" \
375 "(SELECT GROUP_CONCAT(fn:concat( " \
376 "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\
379 "?_role nco:url ?url_val . " \
381 "nie:url(nco:photo(<%s>)) " \
382 "nco:role(?_role) " \
383 "nco:contactUID(<%s>) " \
384 "nco:title(?_role) " \
385 "rdfs:label(?_role) " \
386 "nco:fullname(nco:org(?_role))" \
387 "nco:department(?_role) " \
388 "(SELECT GROUP_CONCAT(fn:concat(?emailaddress,\"\31\"," \
389 "tracker:coalesce(rdfs:label(?_role), \"\"))," \
392 "?_role nco:hasEmailAddress " \
393 " [ nco:emailAddress ?emailaddress ] " \
395 "\"NOTACALL\" \"false\" \"false\" " \
398 " <%s> a nco:PersonContact ." \
399 " OPTIONAL {<%s> nco:hasAffiliation ?_role .}" \
402 #define CONTACTS_OTHER_QUERY_FROM_URI \
403 "SELECT fn:concat(\"TYPE_OTHER\", \"\31\", nco:phoneNumber(?t))"\
404 "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" " \
405 "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" " \
406 " \"NOTACALL\" \"false\" \"false\" <%s> " \
408 "<%s> a nco:Contact . " \
409 "OPTIONAL { <%s> nco:hasPhoneNumber ?t . } " \
412 #define CONTACTS_COUNT_QUERY \
413 "SELECT COUNT(?c) " \
415 "?c a nco:PersonContact ." \
418 #define MISSED_CALLS_COUNT_QUERY \
419 "SELECT COUNT(?call) WHERE {" \
420 "?c a nco:Contact ;" \
421 "nco:hasPhoneNumber ?h ." \
422 "?call a nmo:Call ;" \
423 "nmo:isSent false ;" \
425 "nmo:isAnswered false ." \
428 #define INCOMING_CALLS_COUNT_QUERY \
429 "SELECT COUNT(?call) WHERE {" \
430 "?c a nco:Contact ;" \
431 "nco:hasPhoneNumber ?h ." \
432 "?call a nmo:Call ;" \
433 "nmo:isSent false ;" \
435 "nmo:isAnswered true ." \
438 #define OUTGOING_CALLS_COUNT_QUERY \
439 "SELECT COUNT(?call) WHERE {" \
440 "?c a nco:Contact ;" \
441 "nco:hasPhoneNumber ?h ." \
442 "?call a nmo:Call ;" \
443 "nmo:isSent true ;" \
447 #define COMBINED_CALLS_COUNT_QUERY \
448 "SELECT COUNT(?call) WHERE {" \
450 "?c a nco:Contact ;" \
451 "nco:hasPhoneNumber ?h ." \
452 "?call a nmo:Call ;" \
453 "nmo:isSent true ;" \
456 "?c a nco:Contact ;" \
457 "nco:hasPhoneNumber ?h ." \
458 "?call a nmo:Call ;" \
463 #define NEW_MISSED_CALLS_COUNT_QUERY \
464 "SELECT COUNT(?call) WHERE {" \
465 "?c a nco:Contact ;" \
466 "nco:hasPhoneNumber ?h ." \
467 "?call a nmo:Call ;" \
468 "nmo:isSent false ;" \
470 "nmo:isAnswered false ;" \
471 "nmo:isRead false ." \
474 typedef int (*reply_list_foreach_t) (const char **reply, int num_fields,
477 typedef void (*add_field_t) (struct phonebook_contact *contact,
478 const char *value, int type);
480 struct pending_reply {
481 reply_list_foreach_t callback;
486 struct contact_data {
488 struct phonebook_contact *contact;
491 struct phonebook_data {
496 const struct apparam_field *params;
498 phonebook_cache_ready_cb ready_cb;
499 phonebook_entry_cb entry_cb;
501 GCancellable *query_canc;
503 int vcard_part_count;
507 struct phonebook_index {
512 static TrackerSparqlConnection *connection = NULL;
514 static const char *name2query(const char *name)
516 if (g_str_equal(name, "/telecom/pb.vcf"))
517 return CONTACTS_QUERY_ALL;
518 else if (g_str_equal(name, "/telecom/ich.vcf"))
519 return INCOMING_CALLS_QUERY;
520 else if (g_str_equal(name, "/telecom/och.vcf"))
521 return OUTGOING_CALLS_QUERY;
522 else if (g_str_equal(name, "/telecom/mch.vcf"))
523 return MISSED_CALLS_QUERY;
524 else if (g_str_equal(name, "/telecom/cch.vcf"))
525 return COMBINED_CALLS_QUERY;
530 static const char *name2count_query(const char *name)
532 if (g_str_equal(name, "/telecom/pb.vcf"))
533 return CONTACTS_COUNT_QUERY;
534 else if (g_str_equal(name, "/telecom/ich.vcf"))
535 return INCOMING_CALLS_COUNT_QUERY;
536 else if (g_str_equal(name, "/telecom/och.vcf"))
537 return OUTGOING_CALLS_COUNT_QUERY;
538 else if (g_str_equal(name, "/telecom/mch.vcf"))
539 return MISSED_CALLS_COUNT_QUERY;
540 else if (g_str_equal(name, "/telecom/cch.vcf"))
541 return COMBINED_CALLS_COUNT_QUERY;
546 static gboolean folder_is_valid(const char *folder)
551 if (g_str_equal(folder, "/"))
553 else if (g_str_equal(folder, "/telecom"))
555 else if (g_str_equal(folder, "/telecom/pb"))
557 else if (g_str_equal(folder, "/telecom/ich"))
559 else if (g_str_equal(folder, "/telecom/och"))
561 else if (g_str_equal(folder, "/telecom/mch"))
563 else if (g_str_equal(folder, "/telecom/cch"))
569 static const char *folder2query(const char *folder)
571 if (g_str_equal(folder, "/telecom/pb"))
572 return CONTACTS_QUERY_ALL_LIST;
573 else if (g_str_equal(folder, "/telecom/ich"))
574 return INCOMING_CALLS_LIST;
575 else if (g_str_equal(folder, "/telecom/och"))
576 return OUTGOING_CALLS_LIST;
577 else if (g_str_equal(folder, "/telecom/mch"))
578 return MISSED_CALLS_LIST;
579 else if (g_str_equal(folder, "/telecom/cch"))
580 return COMBINED_CALLS_LIST;
585 static const char **string_array_from_cursor(TrackerSparqlCursor *cursor,
591 result = g_new0(const char *, array_len);
593 for (i = 0; i < array_len; ++i) {
594 TrackerSparqlValueType type;
596 type = tracker_sparql_cursor_get_value_type(cursor, i);
598 if (type == TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE ||
599 type == TRACKER_SPARQL_VALUE_TYPE_UNBOUND)
600 /* For null/unbound type filling result part with ""*/
603 /* Filling with string representation of content*/
604 result[i] = tracker_sparql_cursor_get_string(cursor, i,
611 static void update_cancellable(struct phonebook_data *pdata,
614 if (pdata->query_canc)
615 g_object_unref(pdata->query_canc);
617 pdata->query_canc = canc;
620 static void async_query_cursor_next_cb(GObject *source, GAsyncResult *result,
623 struct pending_reply *pending = user_data;
624 TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR(source);
625 GCancellable *cancellable;
626 GError *error = NULL;
631 success = tracker_sparql_cursor_next_finish(
632 TRACKER_SPARQL_CURSOR(source),
637 DBG("cursor_next error: %s", error->message);
640 /* When tracker_sparql_cursor_next_finish ends with
641 * failure and no error is set, that means end of
642 * results returned by query */
643 pending->callback(NULL, 0, pending->user_data);
648 node = string_array_from_cursor(cursor, pending->num_fields);
649 err = pending->callback(node, pending->num_fields, pending->user_data);
652 /* Fetch next result only if processing current chunk ended with
653 * success. Sometimes during processing data, we are able to determine
654 * if there is no need to get more data from tracker - by example
655 * stored amount of data parts is big enough for sending and we might
656 * want to suspend processing or just some error occurred. */
658 cancellable = g_cancellable_new();
659 update_cancellable(pending->user_data, cancellable);
660 tracker_sparql_cursor_next_async(cursor, cancellable,
661 async_query_cursor_next_cb,
667 g_object_unref(cursor);
671 static int query_tracker(const char *query, int num_fields,
672 reply_list_foreach_t callback, void *user_data)
674 struct pending_reply *pending;
675 GCancellable *cancellable;
676 TrackerSparqlCursor *cursor;
677 GError *error = NULL;
681 if (connection == NULL)
682 connection = tracker_sparql_connection_get_direct(
687 DBG("direct-connection error: %s", error->message);
694 cancellable = g_cancellable_new();
695 update_cancellable(user_data, cancellable);
696 cursor = tracker_sparql_connection_query(connection, query,
697 cancellable, &error);
699 if (cursor == NULL) {
701 DBG("connection_query error: %s", error->message);
705 g_object_unref(cancellable);
710 pending = g_new0(struct pending_reply, 1);
711 pending->callback = callback;
712 pending->user_data = user_data;
713 pending->num_fields = num_fields;
715 /* Now asynchronously going through each row of results - callback
716 * async_query_cursor_next_cb will be called ALWAYS, even if async
717 * request was canceled */
718 tracker_sparql_cursor_next_async(cursor, cancellable,
719 async_query_cursor_next_cb,
725 static char *iso8601_utc_to_localtime(const char *datetime)
728 struct tm tm, *local;
732 memset(&tm, 0, sizeof(tm));
734 nr = sscanf(datetime, "%04u-%02u-%02uT%02u:%02u:%02u",
735 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
736 &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
738 /* Invalid time format */
739 error("sscanf(): %s (%d)", strerror(errno), errno);
743 /* Time already in localtime */
744 if (!g_str_has_suffix(datetime, "Z")) {
745 strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", &tm);
746 return g_strdup(localdate);
749 tm.tm_year -= 1900; /* Year since 1900 */
750 tm.tm_mon--; /* Months since January, values 0-11 */
755 local = localtime(&time);
757 strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", local);
759 return g_strdup(localdate);
762 static void set_call_type(struct phonebook_contact *contact,
763 const char *datetime, const char *is_sent,
764 const char *is_answered)
766 gboolean sent, answered;
768 if (g_strcmp0(datetime, "NOTACALL") == 0) {
769 contact->calltype = CALL_TYPE_NOT_A_CALL;
773 sent = g_str_equal(is_sent, "true");
774 answered = g_str_equal(is_answered, "true");
777 if (answered == FALSE)
778 contact->calltype = CALL_TYPE_MISSED;
780 contact->calltype = CALL_TYPE_INCOMING;
782 contact->calltype = CALL_TYPE_OUTGOING;
784 /* Tracker gives time in the ISO 8601 format, UTC time */
785 contact->datetime = iso8601_utc_to_localtime(datetime);
788 static gboolean contact_matches(struct contact_data *c_data, const char *id,
789 const char *datetime)
794 if (g_strcmp0(c_data->id, id) != 0)
797 /* id is equal and not call history entry => contact matches */
798 if (c_data->contact->calltype == CALL_TYPE_NOT_A_CALL)
801 /* for call history entries have to compare also timestamps of calls */
802 localtime = iso8601_utc_to_localtime(datetime);
803 cmp_ret = g_strcmp0(c_data->contact->datetime, localtime);
806 return (cmp_ret == 0) ? TRUE : FALSE;
809 static struct phonebook_contact *find_contact(GSList *contacts, const char *id,
810 const char *datetime)
814 for (l = contacts; l; l = l->next) {
815 struct contact_data *c_data = l->data;
817 if (contact_matches(c_data, id, datetime))
818 return c_data->contact;
824 static struct phonebook_field *find_field(GSList *fields, const char *value,
829 for (l = fields; l; l = l->next) {
830 struct phonebook_field *field = l->data;
831 /* Returning phonebook number if phone values and type values
833 if (g_strcmp0(field->text, value) == 0 && field->type == type)
840 static void add_phone_number(struct phonebook_contact *contact,
841 const char *phone, int type)
843 struct phonebook_field *number;
845 if (phone == NULL || strlen(phone) == 0)
848 /* Not adding number if there is already added with the same value */
849 if (find_field(contact->numbers, phone, type))
852 number = g_new0(struct phonebook_field, 1);
853 number->text = g_strdup(phone);
856 contact->numbers = g_slist_append(contact->numbers, number);
859 static void add_email(struct phonebook_contact *contact, const char *address,
862 struct phonebook_field *email;
864 if (address == NULL || strlen(address) == 0)
867 /* Not adding email if there is already added with the same value */
868 if (find_field(contact->emails, address, type))
871 email = g_new0(struct phonebook_field, 1);
872 email->text = g_strdup(address);
875 contact->emails = g_slist_append(contact->emails, email);
878 static gboolean addr_matches(struct phonebook_addr *a, struct phonebook_addr *b)
882 if (a->type != b->type)
885 for (la = a->fields, lb = b->fields; la && lb;
886 la = la->next, lb = lb->next) {
887 char *field_a = la->data;
888 char *field_b = lb->data;
890 if (g_strcmp0(field_a, field_b) != 0)
897 /* generates phonebook_addr struct from tracker address data string. */
898 static struct phonebook_addr *gen_addr(const char *address, int type)
900 struct phonebook_addr *addr;
901 GSList *fields = NULL;
905 /* This test handles cases when address points to empty string
906 * (or address is NULL pointer) or string containing only six
907 * separators. It indicates that none of address fields is present
908 * and there is no sense to create dummy phonebook_addr struct */
909 if (address == NULL || strlen(address) < ADDR_FIELD_AMOUNT)
912 addr_parts = g_strsplit(address, ADDR_DELIM, ADDR_FIELD_AMOUNT);
914 for (i = 0; i < ADDR_FIELD_AMOUNT; ++i)
915 fields = g_slist_append(fields, g_strdup(addr_parts[i]));
917 g_strfreev(addr_parts);
919 addr = g_new0(struct phonebook_addr, 1);
920 addr->fields = fields;
926 static void add_address(struct phonebook_contact *contact,
927 const char *address, int type)
929 struct phonebook_addr *addr;
932 addr = gen_addr(address, type);
936 /* Not adding address if there is already added with the same value.
937 * These type of checks have to be done because sometimes tracker
938 * returns results for contact data in more than 1 row - then the same
939 * address may be returned more than once in query results */
940 for (l = contact->addresses; l; l = l->next) {
941 struct phonebook_addr *tmp = l->data;
943 if (addr_matches(tmp, addr)) {
944 phonebook_addr_free(addr);
949 contact->addresses = g_slist_append(contact->addresses, addr);
952 static void add_url(struct phonebook_contact *contact, const char *url_val,
955 struct phonebook_field *url;
957 if (url_val == NULL || strlen(url_val) == 0)
960 /* Not adding url if there is already added with the same value */
961 if (find_field(contact->urls, url_val, type))
964 url = g_new0(struct phonebook_field, 1);
966 url->text = g_strdup(url_val);
969 contact->urls = g_slist_append(contact->urls, url);
972 static GString *gen_vcards(GSList *contacts,
973 const struct apparam_field *params)
978 vcards = g_string_new(NULL);
980 /* Generating VCARD string from contacts and freeing used contacts */
981 for (l = contacts; l; l = l->next) {
982 struct contact_data *c_data = l->data;
983 phonebook_add_contact(vcards, c_data->contact,
984 params->filter, params->format);
990 static int pull_contacts_size(const char **reply, int num_fields,
993 struct phonebook_data *data = user_data;
995 if (num_fields < 0) {
996 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
1000 if (reply != NULL) {
1001 data->index = atoi(reply[0]);
1005 data->cb(NULL, 0, data->index, data->newmissedcalls, TRUE,
1010 * phonebook_data is freed in phonebook_req_finalize. Useful in
1011 * cases when call is terminated.
1015 static void add_affiliation(char **field, const char *value)
1017 if (strlen(*field) > 0 || value == NULL || strlen(value) == 0)
1022 *field = g_strdup(value);
1025 static void contact_init(struct phonebook_contact *contact,
1028 if (reply[COL_FAMILY_NAME][0] == '\0' &&
1029 reply[COL_GIVEN_NAME][0] == '\0' &&
1030 reply[COL_ADDITIONAL_NAME][0] == '\0' &&
1031 reply[COL_NAME_PREFIX][0] == '\0' &&
1032 reply[COL_NAME_SUFFIX][0] == '\0') {
1033 if (reply[COL_FULL_NAME][0] != '\0')
1034 contact->family = g_strdup(reply[COL_FULL_NAME]);
1036 contact->family = g_strdup(reply[COL_NICKNAME]);
1038 contact->family = g_strdup(reply[COL_FAMILY_NAME]);
1039 contact->given = g_strdup(reply[COL_GIVEN_NAME]);
1040 contact->additional = g_strdup(reply[COL_ADDITIONAL_NAME]);
1041 contact->prefix = g_strdup(reply[COL_NAME_PREFIX]);
1042 contact->suffix = g_strdup(reply[COL_NAME_SUFFIX]);
1044 contact->fullname = g_strdup(reply[COL_FULL_NAME]);
1045 contact->birthday = g_strdup(reply[COL_BIRTH_DATE]);
1046 contact->nickname = g_strdup(reply[COL_NICKNAME]);
1047 contact->photo = g_strdup(reply[COL_PHOTO]);
1048 contact->company = g_strdup(reply[COL_ORG_NAME]);
1049 contact->department = g_strdup(reply[COL_ORG_DEPARTMENT]);
1050 contact->role = g_strdup(reply[COL_ORG_ROLE]);
1051 contact->uid = g_strdup(reply[COL_UID]);
1052 contact->title = g_strdup(reply[COL_TITLE]);
1054 set_call_type(contact, reply[COL_DATE], reply[COL_SENT],
1055 reply[COL_ANSWERED]);
1058 static enum phonebook_number_type get_phone_type(const char *affilation)
1060 if (g_strcmp0(AFFILATION_HOME, affilation) == 0)
1061 return TEL_TYPE_HOME;
1062 else if (g_strcmp0(AFFILATION_WORK, affilation) == 0)
1063 return TEL_TYPE_WORK;
1065 return TEL_TYPE_OTHER;
1068 static void add_aff_number(struct phonebook_contact *contact,
1069 const char *pnumber, const char *aff_type)
1072 char *type, *number;
1074 /* For phone taken directly from contacts data, phone number string
1075 * is represented as number type and number string - those strings are
1076 * separated by SUB_DELIM string */
1077 num_parts = g_strsplit(pnumber, SUB_DELIM, 2);
1083 type = num_parts[0];
1088 number = num_parts[1];
1092 if (g_strrstr(type, FAX_NUM_TYPE))
1093 add_phone_number(contact, number, TEL_TYPE_FAX);
1094 else if (g_strrstr(type, MOBILE_NUM_TYPE))
1095 add_phone_number(contact, number, TEL_TYPE_MOBILE);
1097 /* if this is no fax/mobile phone, then adding phone number
1098 * type based on type of the affilation field */
1099 add_phone_number(contact, number, get_phone_type(aff_type));
1102 g_strfreev(num_parts);
1105 static void contact_add_numbers(struct phonebook_contact *contact,
1111 /* Filling phone numbers from contact's affilation */
1112 aff_numbers = g_strsplit(reply[COL_PHONE_AFF], MAIN_DELIM, MAX_FIELDS);
1115 for (i = 0; aff_numbers[i]; ++i)
1116 add_aff_number(contact, aff_numbers[i],
1117 reply[COL_AFF_TYPE]);
1119 g_strfreev(aff_numbers);
1122 static enum phonebook_field_type get_field_type(const char *affilation)
1124 if (g_strcmp0(AFFILATION_HOME, affilation) == 0)
1125 return FIELD_TYPE_HOME;
1126 else if (g_strcmp0(AFFILATION_WORK, affilation) == 0)
1127 return FIELD_TYPE_WORK;
1129 return FIELD_TYPE_OTHER;
1132 static void add_aff_field(struct phonebook_contact *contact,
1133 const char *aff_email, add_field_t add_field_cb)
1138 /* Emails from affilation data, are represented as real email
1139 * string and affilation type - those strings are separated by
1140 * SUB_DELIM string */
1141 email_parts = g_strsplit(aff_email, SUB_DELIM, 2);
1147 email = email_parts[0];
1152 type = email_parts[1];
1156 add_field_cb(contact, email, get_field_type(type));
1159 g_strfreev(email_parts);
1162 static void contact_add_emails(struct phonebook_contact *contact,
1168 /* Emails from affilation */
1169 aff_emails = g_strsplit(reply[COL_EMAIL_AFF], MAIN_DELIM, MAX_FIELDS);
1172 for (i = 0; aff_emails[i] != NULL; ++i)
1173 add_aff_field(contact, aff_emails[i], add_email);
1175 g_strfreev(aff_emails);
1178 static void contact_add_addresses(struct phonebook_contact *contact,
1184 /* Addresses from affilation */
1185 aff_addr = g_strsplit(reply[COL_ADDR_AFF], MAIN_DELIM, MAX_FIELDS);
1188 for (i = 0; aff_addr[i] != NULL; ++i)
1189 add_aff_field(contact, aff_addr[i], add_address);
1191 g_strfreev(aff_addr);
1194 static void contact_add_urls(struct phonebook_contact *contact,
1200 /* Addresses from affilation */
1201 aff_url = g_strsplit(reply[COL_URL], MAIN_DELIM, MAX_FIELDS);
1204 for (i = 0; aff_url[i] != NULL; ++i)
1205 add_aff_field(contact, aff_url[i], add_url);
1207 g_strfreev(aff_url);
1210 static void contact_add_organization(struct phonebook_contact *contact,
1213 /* Adding fields connected by nco:hasAffiliation - they may be in
1214 * separate replies */
1215 add_affiliation(&contact->title, reply[COL_TITLE]);
1216 add_affiliation(&contact->company, reply[COL_ORG_NAME]);
1217 add_affiliation(&contact->department, reply[COL_ORG_DEPARTMENT]);
1218 add_affiliation(&contact->role, reply[COL_ORG_ROLE]);
1221 static void free_data_contacts(struct phonebook_data *data)
1225 /* freeing contacts */
1226 for (l = data->contacts; l; l = l->next) {
1227 struct contact_data *c_data = l->data;
1230 phonebook_contact_free(c_data->contact);
1234 g_slist_free(data->contacts);
1235 data->contacts = NULL;
1238 static void send_pull_part(struct phonebook_data *data,
1239 const struct apparam_field *params, gboolean lastpart)
1244 vcards = gen_vcards(data->contacts, params);
1245 data->cb(vcards->str, vcards->len, g_slist_length(data->contacts),
1246 data->newmissedcalls, lastpart, data->user_data);
1249 free_data_contacts(data);
1250 g_string_free(vcards, TRUE);
1253 static int pull_contacts(const char **reply, int num_fields, void *user_data)
1255 struct phonebook_data *data = user_data;
1256 const struct apparam_field *params = data->params;
1257 struct phonebook_contact *contact;
1258 struct contact_data *contact_data;
1260 gboolean cdata_present = FALSE, part_sent = FALSE;
1261 static char *temp_id = NULL;
1263 if (num_fields < 0) {
1264 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
1268 DBG("reply %p", reply);
1269 data->tracker_index++;
1274 /* Trying to find contact in recently added contacts. It is needed for
1275 * contacts that have more than one telephone number filled */
1276 contact = find_contact(data->contacts, reply[CONTACTS_ID_COL],
1279 /* If contact is already created then adding only new phone numbers */
1281 cdata_present = TRUE;
1285 /* We are doing a PullvCardEntry, no need for those checks */
1286 if (data->vcardentry)
1289 /* Last four fields are always present, ignoring them */
1290 for (i = 0; i < num_fields - 4; i++) {
1291 if (reply[i][0] != '\0')
1295 if (i == num_fields - 4 && !g_str_equal(reply[CONTACTS_ID_COL],
1296 TRACKER_DEFAULT_CONTACT_ME))
1299 if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) {
1302 temp_id = g_strdup(reply[CONTACTS_ID_COL]);
1304 /* Incrementing counter for vcards in current part of data,
1305 * but only if liststartoffset has been already reached */
1306 if (data->index > params->liststartoffset)
1307 data->vcard_part_count++;
1310 if (data->vcard_part_count > VCARDS_PART_COUNT) {
1311 DBG("Part of vcard data ready for sending...");
1312 data->vcard_part_count = 0;
1313 /* Sending part of data to PBAP core - more data can be still
1314 * fetched, so marking lastpart as FALSE */
1315 send_pull_part(data, params, FALSE);
1317 /* Later, after adding contact data, need to return -EINTR to
1318 * stop fetching more data for this request. Data will be
1319 * downloaded again from this point, when phonebook_pull_read
1320 * will be called again with current request as a parameter*/
1324 last_index = params->liststartoffset + params->maxlistcount;
1326 if (data->index <= params->liststartoffset)
1329 /* max number of results achieved - need send vcards data that was
1330 * already collected and stop further data processing (these operations
1331 * will be invoked in "done" section) */
1332 if (data->index > last_index && params->maxlistcount > 0) {
1333 DBG("Maxlistcount achieved");
1338 contact = g_new0(struct phonebook_contact, 1);
1339 contact_init(contact, reply);
1342 contact_add_numbers(contact, reply);
1343 contact_add_emails(contact, reply);
1344 contact_add_addresses(contact, reply);
1345 contact_add_urls(contact, reply);
1346 contact_add_organization(contact, reply);
1348 DBG("contact %p", contact);
1350 /* Adding contacts data to wrapper struct - this data will be used to
1351 * generate vcard list */
1352 if (!cdata_present) {
1353 contact_data = g_new0(struct contact_data, 1);
1354 contact_data->contact = contact;
1355 contact_data->id = g_strdup(reply[CONTACTS_ID_COL]);
1356 data->contacts = g_slist_append(data->contacts, contact_data);
1365 /* Processing is end, this is definitely last part of transmission
1366 * (marking lastpart as TRUE) */
1367 send_pull_part(data, params, TRUE);
1375 * phonebook_data is freed in phonebook_req_finalize. Useful in
1376 * cases when call is terminated.
1380 static int add_to_cache(const char **reply, int num_fields, void *user_data)
1382 struct phonebook_data *data = user_data;
1386 if (reply == NULL || num_fields < 0)
1389 /* the first element is the URI, always not empty */
1390 for (i = 1; i < num_fields; i++) {
1391 if (reply[i][0] != '\0')
1395 if (i == num_fields &&
1396 !g_str_equal(reply[0], TRACKER_DEFAULT_CONTACT_ME))
1400 formatted = g_strdup(reply[7]);
1402 formatted = g_strdup(reply[6]);
1404 formatted = g_strdup_printf("%s;%s;%s;%s;%s",
1405 reply[1], reply[2], reply[3], reply[4],
1408 /* The owner vCard must have the 0 handle */
1409 if (strcmp(reply[0], TRACKER_DEFAULT_CONTACT_ME) == 0)
1410 data->entry_cb(reply[0], 0, formatted, "",
1411 reply[6], data->user_data);
1413 data->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted,
1414 "", reply[6], data->user_data);
1421 if (num_fields <= 0)
1422 data->ready_cb(data->user_data);
1426 * phonebook_data is freed in phonebook_req_finalize. Useful in
1427 * cases when call is terminated.
1431 int phonebook_init(void)
1433 g_thread_init(NULL);
1439 void phonebook_exit(void)
1443 char *phonebook_set_folder(const char *current_folder, const char *new_folder,
1444 uint8_t flags, int *err)
1446 char *tmp1, *tmp2, *base, *path = NULL;
1447 gboolean root, child;
1451 root = (g_strcmp0("/", current_folder) == 0);
1452 child = (new_folder && strlen(new_folder) != 0);
1456 /* Go back to root */
1458 path = g_strdup("/");
1462 path = g_build_filename(current_folder, new_folder, NULL);
1468 path = g_strdup("/");
1473 * Removing one level of the current folder. Current folder
1474 * contains AT LEAST one level since it is not at root folder.
1475 * Use glib utility functions to handle invalid chars in the
1476 * folder path properly.
1478 tmp1 = g_path_get_basename(current_folder);
1479 tmp2 = g_strrstr(current_folder, tmp1);
1480 len = tmp2 - (current_folder + 1);
1485 base = g_strdup("/");
1487 base = g_strndup(current_folder, len);
1489 /* Return: one level only */
1495 path = g_build_filename(base, new_folder, NULL);
1505 if (path && !folder_is_valid(path))
1519 static int pull_newmissedcalls(const char **reply, int num_fields,
1522 struct phonebook_data *data = user_data;
1523 reply_list_foreach_t pull_cb;
1524 int col_amount, err;
1528 if (num_fields < 0) {
1529 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
1534 if (reply != NULL) {
1535 nmissed = atoi(reply[0]);
1536 data->newmissedcalls =
1537 nmissed <= UINT8_MAX ? nmissed : UINT8_MAX;
1538 DBG("newmissedcalls %d", data->newmissedcalls);
1543 if (data->params->maxlistcount == 0) {
1544 query = name2count_query("/telecom/mch.vcf");
1545 col_amount = COUNT_QUERY_COL_AMOUNT;
1546 pull_cb = pull_contacts_size;
1548 query = name2query("/telecom/mch.vcf");
1549 col_amount = PULL_QUERY_COL_AMOUNT;
1550 pull_cb = pull_contacts;
1553 err = query_tracker(query, col_amount, pull_cb, data);
1555 data->cb(NULL, 0, err, 0, TRUE, data->user_data);
1563 void phonebook_req_finalize(void *request)
1565 struct phonebook_data *data = request;
1572 /* canceling asynchronous operation on tracker if any is active */
1573 if (data->query_canc) {
1574 g_cancellable_cancel(data->query_canc);
1575 g_object_unref(data->query_canc);
1578 free_data_contacts(data);
1579 g_free(data->req_name);
1583 void *phonebook_pull(const char *name, const struct apparam_field *params,
1584 phonebook_cb cb, void *user_data, int *err)
1586 struct phonebook_data *data;
1588 DBG("name %s", name);
1590 data = g_new0(struct phonebook_data, 1);
1591 data->params = params;
1592 data->user_data = user_data;
1594 data->req_name = g_strdup(name);
1602 int phonebook_pull_read(void *request)
1604 struct phonebook_data *data = request;
1605 reply_list_foreach_t pull_cb;
1614 data->newmissedcalls = 0;
1616 if (g_strcmp0(data->req_name, "/telecom/mch.vcf") == 0 &&
1617 data->tracker_index == 0) {
1618 /* new missed calls amount should be counted only once - it
1619 * will be done during generating first part of results of
1620 * missed calls history */
1621 query = NEW_MISSED_CALLS_COUNT_QUERY;
1622 col_amount = COUNT_QUERY_COL_AMOUNT;
1623 pull_cb = pull_newmissedcalls;
1624 } else if (data->params->maxlistcount == 0) {
1625 query = name2count_query(data->req_name);
1626 col_amount = COUNT_QUERY_COL_AMOUNT;
1627 pull_cb = pull_contacts_size;
1629 query = name2query(data->req_name);
1630 col_amount = PULL_QUERY_COL_AMOUNT;
1631 pull_cb = pull_contacts;
1637 if (pull_cb == pull_contacts && data->tracker_index > 0) {
1638 /* Adding offset to pull query to download next parts of data
1639 * from tracker (phonebook_pull_read may be called many times
1640 * from PBAP core to fetch data partially) */
1641 offset_query = g_strdup_printf(QUERY_OFFSET_FORMAT, query,
1642 data->tracker_index);
1643 ret = query_tracker(offset_query, col_amount, pull_cb, data);
1645 g_free(offset_query);
1650 return query_tracker(query, col_amount, pull_cb, data);
1653 void *phonebook_get_entry(const char *folder, const char *id,
1654 const struct apparam_field *params,
1655 phonebook_cb cb, void *user_data, int *err)
1657 struct phonebook_data *data;
1661 DBG("folder %s id %s", folder, id);
1663 data = g_new0(struct phonebook_data, 1);
1664 data->user_data = user_data;
1665 data->params = params;
1667 data->vcardentry = TRUE;
1669 if (g_str_has_prefix(id, CONTACT_ID_PREFIX) == TRUE ||
1670 g_strcmp0(id, TRACKER_DEFAULT_CONTACT_ME) == 0)
1671 query = g_strdup_printf(CONTACTS_QUERY_FROM_URI, id, id, id, id,
1672 id, id, id, id, id, id, id, id, id);
1673 else if (g_str_has_prefix(id, CALL_ID_PREFIX) == TRUE)
1674 query = g_strdup_printf(CONTACT_FROM_CALL_QUERY, id);
1676 query = g_strdup_printf(CONTACTS_OTHER_QUERY_FROM_URI,
1679 ret = query_tracker(query, PULL_QUERY_COL_AMOUNT, pull_contacts, data);
1688 void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb,
1689 phonebook_cache_ready_cb ready_cb, void *user_data, int *err)
1691 struct phonebook_data *data;
1695 DBG("name %s", name);
1697 query = folder2query(name);
1698 if (query == NULL) {
1704 data = g_new0(struct phonebook_data, 1);
1705 data->entry_cb = entry_cb;
1706 data->ready_cb = ready_cb;
1707 data->user_data = user_data;
1709 ret = query_tracker(query, 8, add_to_cache, data);