upgrade obexd to 0.47
[profile/ivi/obexd.git] / plugins / phonebook-tracker.c
1 /*
2  *  Phonebook access through D-Bus vCard and call history service
3  *
4  *  Copyright (C) 2010  Nokia Corporation
5  *
6  *
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.
11  *
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.
16  *
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
20  *
21  */
22
23 #include <string.h>
24 #include <stdlib.h>
25 #include <time.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <glib.h>
29 #include <dbus/dbus.h>
30 #include <libtracker-sparql/tracker-sparql.h>
31
32 #include "log.h"
33 #include "obex.h"
34 #include "service.h"
35 #include "mimetype.h"
36 #include "phonebook.h"
37 #include "vcard.h"
38
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"
42
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
49
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
60 #define COL_URL 10
61 #define COL_PHOTO 11
62 #define COL_ORG_ROLE 12
63 #define COL_UID 13
64 #define COL_TITLE 14
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) */
69 #define COL_DATE 19
70 #define COL_SENT 20
71 #define COL_ANSWERED 21
72 #define CONTACTS_ID_COL 22
73 #define CONTACT_ID_PREFIX "urn:uuid:"
74 #define CALL_ID_PREFIX "message:"
75
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"
78
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"
85
86 #define CONTACTS_QUERY_ALL                                              \
87 "SELECT "                                                               \
88 "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"                 \
89 "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"                      \
90 "WHERE {"                                                               \
91 "       ?_role nco:hasPhoneNumber ?aff_number"                          \
92 "}) "                                                                   \
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) ), "                                       \
108 "\"\30\") "                                                             \
109 "WHERE {"                                                               \
110 "?_role nco:hasPostalAddress ?aff_addr"                                 \
111 "}) "                                                                   \
112 "nco:birthDate(?_contact) "                                             \
113 "(SELECT "                                                              \
114 "       ?nick "                                                         \
115 "       WHERE { "                                                       \
116 "               { "                                                     \
117 "                       ?_contact nco:nickname ?nick "                  \
118 "               } UNION { "                                             \
119 "                       ?_contact nco:hasAffiliation ?role . "          \
120 "                       ?role nco:hasIMAddress ?im . "                  \
121 "                       ?im nco:imNickname ?nick "                      \
122 "               } "                                                     \
123 "       } "                                                             \
124 ") "                                                                    \
125 "(SELECT GROUP_CONCAT(fn:concat( "                                      \
126         "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\
127         "), \"\30\") "                                                  \
128         "WHERE {"                                                       \
129                 "?_role nco:url ?url_val . "                            \
130 "})"                                                                    \
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), \"\")),"                  \
140         "\"\30\") "                                                     \
141         "WHERE { "                                                      \
142         "?_role nco:hasEmailAddress "                                   \
143         "               [ nco:emailAddress ?emailaddress ] "            \
144         "}) "                                                           \
145 "\"NOTACALL\" \"false\" \"false\" "                                     \
146 "?_contact "                                                            \
147 "WHERE {"                                                               \
148 "       ?_contact a nco:PersonContact ."                                \
149 "       OPTIONAL {?_contact nco:hasAffiliation ?_role .}"               \
150 "}"                                                                     \
151 "ORDER BY tracker:id(?_contact)"
152
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) "      \
157         "(SELECT "                                                      \
158                 "?nick "                                                \
159                 "WHERE { "                                              \
160                         "{ "                                            \
161                                 "?c nco:nickname ?nick "                \
162                         "} UNION { "                                    \
163                                 "?c nco:hasAffiliation ?role . "        \
164                                 "?role nco:hasIMAddress ?im . "         \
165                                 "?im nco:imNickname ?nick "             \
166                         "} "                                            \
167                 "} "                                                    \
168         ") "                                                            \
169         "nco:phoneNumber(?h) "                                          \
170         "WHERE { "                                                      \
171                 "?c a nco:PersonContact . "                             \
172         "OPTIONAL { ?c nco:hasPhoneNumber ?h . } "                      \
173         "OPTIONAL { "                                                   \
174                 "?c nco:hasAffiliation ?a . "                           \
175                 "?a nco:hasPhoneNumber ?h . "                           \
176         "} "                                                            \
177         "} GROUP BY ?c"
178
179 #define CALLS_CONSTRAINTS(CONSTRAINT)                                   \
180 " WHERE { "                                                             \
181         "?_call a nmo:Call . "                                          \
182         "?_unb_contact a nco:Contact . "                                \
183         "?_unb_contact nco:hasPhoneNumber ?_cpn . "                     \
184 CONSTRAINT                                                              \
185         "OPTIONAL { "                                                   \
186                 "{ SELECT ?_contact ?_no ?_role ?_number "              \
187                         "count(?_contact) as ?cnt "                     \
188                 "WHERE { "                                              \
189                         "?_contact a nco:PersonContact . "              \
190                         "{ "                                            \
191                                 "?_contact nco:hasAffiliation ?_role . "\
192                                 "?_role nco:hasPhoneNumber ?_number . " \
193                         "} UNION { "                                    \
194                                 "?_contact nco:hasPhoneNumber ?_number" \
195                         "} "                                            \
196                         "?_number maemo:localPhoneNumber ?_no . "       \
197                 "} GROUP BY ?_no } "                                    \
198                 "FILTER(?cnt = 1) "                                     \
199                 "?_cpn maemo:localPhoneNumber ?_no . "                  \
200         "} "                                                            \
201 "} "
202
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) "                           \
208         "(SELECT "                                                      \
209                 "?nick "                                                \
210                 "WHERE { "                                              \
211                         "{ "                                            \
212                                 "?_contact nco:nickname ?nick "         \
213                         "} UNION { "                                    \
214                                 "?_contact nco:hasAffiliation ?role . " \
215                                 "?role nco:hasIMAddress ?im . "         \
216                                 "?im nco:imNickname ?nick "             \
217                         "} "                                            \
218                 "} "                                                    \
219         ") "                                                            \
220         "nco:phoneNumber(?_cpn) "                                       \
221 CALLS_CONSTRAINTS(CONSTRAINT)                                           \
222 "ORDER BY DESC(nmo:sentDate(?_call)) "
223
224 #define CALLS_QUERY(CONSTRAINT)                                         \
225 "SELECT "                                                               \
226 "(SELECT fn:concat(rdf:type(?role_number),"                             \
227         "\"\31\", nco:phoneNumber(?role_number))"                       \
228         "WHERE {"                                                       \
229         "{"                                                             \
230         "       ?_role nco:hasPhoneNumber ?role_number "                \
231         "       FILTER (?role_number = ?_number)"                       \
232         "} UNION { "                                                    \
233                 "?_unb_contact nco:hasPhoneNumber ?role_number . "      \
234         "       FILTER (!bound(?_role)) "                               \
235         "}"                                                             \
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) ), "                              \
252         "\"\30\") "                                                     \
253         "WHERE {"                                                       \
254         "?_contact nco:hasAffiliation ?c_role . "                       \
255         "?c_role nco:hasPostalAddress ?aff_addr"                        \
256         "}) "                                                           \
257         "nco:birthDate(?_contact) "                                     \
258 "(SELECT "                                                              \
259         "?nick "                                                        \
260         "WHERE { "                                                      \
261         "       { "                                                     \
262         "       ?_contact nco:nickname ?nick "                          \
263         "               } UNION { "                                     \
264         "                       ?_contact nco:hasAffiliation ?role . "  \
265         "                       ?role nco:hasIMAddress ?im . "          \
266         "                       ?im nco:imNickname ?nick "              \
267         "               } "                                             \
268         "       } "                                                     \
269         ") "                                                            \
270 "(SELECT GROUP_CONCAT(fn:concat(?url_value, \"\31\", "                  \
271         "tracker:coalesce(rdfs:label(?c_role), \"\")), \"\30\") "       \
272         "WHERE {"                                                       \
273                 "?_contact nco:hasAffiliation ?c_role . "               \
274                 "?c_role nco:url ?url_value . "                         \
275 "})"                                                                    \
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), \"\")),"                 \
285         "\"\30\") "                                                     \
286         "WHERE { "                                                      \
287         "?_contact nco:hasAffiliation ?c_role . "                       \
288         "?c_role nco:hasEmailAddress "                                  \
289         "               [ nco:emailAddress ?emailaddress ] "            \
290         "}) "                                                           \
291         "nmo:receivedDate(?_call) "                                     \
292         "nmo:isSent(?_call) "                                           \
293         "nmo:isAnswered(?_call) "                                       \
294         "?_call "                                                       \
295 CALLS_CONSTRAINTS(CONSTRAINT)                                           \
296 "ORDER BY DESC(nmo:sentDate(?_call)) "
297
298 #define MISSED_CONSTRAINT               \
299 "?_call nmo:from ?_unb_contact . "      \
300 "?_call nmo:isSent false . "            \
301 "?_call nmo:isAnswered false . "
302
303 #define INCOMING_CONSTRAINT             \
304 "?_call nmo:from ?_unb_contact . "      \
305 "?_call nmo:isSent false . "            \
306 "?_call nmo:isAnswered true . "
307
308 #define OUTGOING_CONSTRAINT             \
309 "?_call nmo:to ?_unb_contact . "        \
310 "?_call nmo:isSent true . "
311
312 #define COMBINED_CONSTRAINT                     \
313 "{ "                                            \
314 "       ?_call nmo:from ?_unb_contact .  "      \
315 "       ?_call nmo:isSent false "               \
316 "} UNION { "                                    \
317 "       ?_call nmo:to ?_unb_contact . "         \
318 "       ?_call nmo:isSent true "                \
319 "} "
320
321 #define CALL_URI_CONSTRAINT     \
322 COMBINED_CONSTRAINT             \
323 "FILTER (?_call = <%s>) "
324
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)
334
335 #define CONTACTS_QUERY_FROM_URI                                         \
336 "SELECT "                                                               \
337 "(SELECT GROUP_CONCAT(fn:concat(rdf:type(?aff_number),"                 \
338 "\"\31\", nco:phoneNumber(?aff_number)), \"\30\")"                      \
339 "WHERE {"                                                               \
340 "       ?_role nco:hasPhoneNumber ?aff_number"                          \
341 "}) "                                                                   \
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) ), "                                       \
357 "\"\30\") "                                                             \
358 "WHERE {"                                                               \
359 "?_role nco:hasPostalAddress ?aff_addr"                                 \
360 "}) "                                                                   \
361 "nco:birthDate(<%s>) "                                                  \
362 "(SELECT "                                                              \
363 "       ?nick "                                                         \
364 "       WHERE { "                                                       \
365 "               { "                                                     \
366 "                       ?_contact nco:nickname ?nick "                  \
367 "               } UNION { "                                             \
368 "                       ?_contact nco:hasAffiliation ?role . "          \
369 "                       ?role nco:hasIMAddress ?im . "                  \
370 "                       ?im nco:imNickname ?nick "                      \
371 "               } "                                                     \
372 "               FILTER (?_contact = <%s>)"                              \
373 "       } "                                                             \
374 ") "                                                                    \
375 "(SELECT GROUP_CONCAT(fn:concat( "                                      \
376         "?url_val, \"\31\", tracker:coalesce(rdfs:label(?_role), \"\") "\
377         "), \"\30\") "                                                  \
378         "WHERE {"                                                       \
379                 "?_role nco:url ?url_val . "                            \
380 "})"                                                                    \
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), \"\")),"                  \
390         "\"\30\") "                                                     \
391         "WHERE { "                                                      \
392         "?_role nco:hasEmailAddress "                                   \
393         "               [ nco:emailAddress ?emailaddress ] "            \
394         "}) "                                                           \
395 "\"NOTACALL\" \"false\" \"false\" "                                     \
396 "<%s> "                                                                 \
397 "WHERE {"                                                               \
398 "       <%s> a nco:PersonContact ."                                     \
399 "       OPTIONAL {<%s> nco:hasAffiliation ?_role .}"                    \
400 "}"
401
402 #define CONTACTS_OTHER_QUERY_FROM_URI                                   \
403         "SELECT fn:concat(\"TYPE_OTHER\", \"\31\", nco:phoneNumber(?t))"\
404         "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "                      \
405         "\"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" \"\" "            \
406         " \"NOTACALL\" \"false\" \"false\" <%s> "                       \
407         "WHERE { "                                                      \
408                 "<%s> a nco:Contact . "                                 \
409                 "OPTIONAL { <%s> nco:hasPhoneNumber ?t . } "            \
410         "} "
411
412 #define CONTACTS_COUNT_QUERY                                            \
413         "SELECT COUNT(?c) "                                             \
414         "WHERE {"                                                       \
415                 "?c a nco:PersonContact ."                              \
416         "}"
417
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 ;"                                    \
424                 "nmo:from ?c ;"                                         \
425                 "nmo:isAnswered false ."                                \
426         "}"
427
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 ;"                                    \
434                 "nmo:from ?c ;"                                         \
435                 "nmo:isAnswered true ."                                 \
436         "}"
437
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 ;"                                     \
444                 "nmo:to ?c ."                                           \
445         "}"
446
447 #define COMBINED_CALLS_COUNT_QUERY                                      \
448         "SELECT COUNT(?call) WHERE {"                                   \
449         "{"                                                             \
450                 "?c a nco:Contact ;"                                    \
451                 "nco:hasPhoneNumber ?h ."                               \
452                 "?call a nmo:Call ;"                                    \
453                 "nmo:isSent true ;"                                     \
454                 "nmo:to ?c ."                                           \
455         "}UNION {"                                                      \
456                 "?c a nco:Contact ;"                                    \
457                 "nco:hasPhoneNumber ?h ."                               \
458                 "?call a nmo:Call ;"                                    \
459                 "nmo:from ?c ."                                         \
460         "}"                                                             \
461         "}"
462
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 ;"                                    \
469                 "nmo:from ?c ;"                                         \
470                 "nmo:isAnswered false ;"                                \
471                 "nmo:isRead false ."                                    \
472         "}"
473
474 typedef int (*reply_list_foreach_t) (const char **reply, int num_fields,
475                                                         void *user_data);
476
477 typedef void (*add_field_t) (struct phonebook_contact *contact,
478                                                 const char *value, int type);
479
480 struct pending_reply {
481         reply_list_foreach_t callback;
482         void *user_data;
483         int num_fields;
484 };
485
486 struct contact_data {
487         char *id;
488         struct phonebook_contact *contact;
489 };
490
491 struct phonebook_data {
492         phonebook_cb cb;
493         void *user_data;
494         int index;
495         gboolean vcardentry;
496         const struct apparam_field *params;
497         GSList *contacts;
498         phonebook_cache_ready_cb ready_cb;
499         phonebook_entry_cb entry_cb;
500         int newmissedcalls;
501         GCancellable *query_canc;
502         char *req_name;
503         int vcard_part_count;
504         int tracker_index;
505 };
506
507 struct phonebook_index {
508         GArray *phonebook;
509         int index;
510 };
511
512 static TrackerSparqlConnection *connection = NULL;
513
514 static const char *name2query(const char *name)
515 {
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;
526
527         return NULL;
528 }
529
530 static const char *name2count_query(const char *name)
531 {
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;
542
543         return NULL;
544 }
545
546 static gboolean folder_is_valid(const char *folder)
547 {
548         if (folder == NULL)
549                 return FALSE;
550
551         if (g_str_equal(folder, "/"))
552                 return TRUE;
553         else if (g_str_equal(folder, "/telecom"))
554                 return TRUE;
555         else if (g_str_equal(folder, "/telecom/pb"))
556                 return TRUE;
557         else if (g_str_equal(folder, "/telecom/ich"))
558                 return TRUE;
559         else if (g_str_equal(folder, "/telecom/och"))
560                 return TRUE;
561         else if (g_str_equal(folder, "/telecom/mch"))
562                 return TRUE;
563         else if (g_str_equal(folder, "/telecom/cch"))
564                 return TRUE;
565
566         return FALSE;
567 }
568
569 static const char *folder2query(const char *folder)
570 {
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;
581
582         return NULL;
583 }
584
585 static const char **string_array_from_cursor(TrackerSparqlCursor *cursor,
586                                                                 int array_len)
587 {
588         const char **result;
589         int i;
590
591         result = g_new0(const char *, array_len);
592
593         for (i = 0; i < array_len; ++i) {
594                 TrackerSparqlValueType type;
595
596                 type = tracker_sparql_cursor_get_value_type(cursor, i);
597
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 ""*/
601                         result[i] = "";
602                 else
603                         /* Filling with string representation of content*/
604                         result[i] = tracker_sparql_cursor_get_string(cursor, i,
605                                                                         NULL);
606         }
607
608         return result;
609 }
610
611 static void update_cancellable(struct phonebook_data *pdata,
612                                                         GCancellable *canc)
613 {
614         if (pdata->query_canc)
615                 g_object_unref(pdata->query_canc);
616
617         pdata->query_canc = canc;
618 }
619
620 static void async_query_cursor_next_cb(GObject *source, GAsyncResult *result,
621                                                         gpointer user_data)
622 {
623         struct pending_reply *pending = user_data;
624         TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR(source);
625         GCancellable *cancellable;
626         GError *error = NULL;
627         gboolean success;
628         const char **node;
629         int err;
630
631         success = tracker_sparql_cursor_next_finish(
632                                                 TRACKER_SPARQL_CURSOR(source),
633                                                 result, &error);
634
635         if (!success) {
636                 if (error) {
637                         DBG("cursor_next error: %s", error->message);
638                         g_error_free(error);
639                 } else
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);
644
645                 goto failed;
646         }
647
648         node = string_array_from_cursor(cursor, pending->num_fields);
649         err = pending->callback(node, pending->num_fields, pending->user_data);
650         g_free(node);
651
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. */
657         if (!err) {
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,
662                                                 pending);
663                 return;
664         }
665
666 failed:
667         g_object_unref(cursor);
668         g_free(pending);
669 }
670
671 static int query_tracker(const char *query, int num_fields,
672                                 reply_list_foreach_t callback, void *user_data)
673 {
674         struct pending_reply *pending;
675         GCancellable *cancellable;
676         TrackerSparqlCursor *cursor;
677         GError *error = NULL;
678
679         DBG("");
680
681         if (connection == NULL)
682                 connection = tracker_sparql_connection_get_direct(
683                                                                 NULL, &error);
684
685         if (!connection) {
686                 if (error) {
687                         DBG("direct-connection error: %s", error->message);
688                         g_error_free(error);
689                 }
690
691                 return -EINTR;
692         }
693
694         cancellable = g_cancellable_new();
695         update_cancellable(user_data, cancellable);
696         cursor = tracker_sparql_connection_query(connection, query,
697                                                         cancellable, &error);
698
699         if (cursor == NULL) {
700                 if (error) {
701                         DBG("connection_query error: %s", error->message);
702                         g_error_free(error);
703                 }
704
705                 g_object_unref(cancellable);
706
707                 return -EINTR;
708         }
709
710         pending = g_new0(struct pending_reply, 1);
711         pending->callback = callback;
712         pending->user_data = user_data;
713         pending->num_fields = num_fields;
714
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,
720                                                 pending);
721
722         return 0;
723 }
724
725 static char *iso8601_utc_to_localtime(const char *datetime)
726 {
727         time_t time;
728         struct tm tm, *local;
729         char localdate[32];
730         int nr;
731
732         memset(&tm, 0, sizeof(tm));
733
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);
737         if (nr < 6) {
738                 /* Invalid time format */
739                 error("sscanf(): %s (%d)", strerror(errno), errno);
740                 return g_strdup("");
741         }
742
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);
747         }
748
749         tm.tm_year -= 1900;     /* Year since 1900 */
750         tm.tm_mon--;            /* Months since January, values 0-11 */
751
752         time = mktime(&tm);
753         time -= timezone;
754
755         local = localtime(&time);
756
757         strftime(localdate, sizeof(localdate), "%Y%m%dT%H%M%S", local);
758
759         return g_strdup(localdate);
760 }
761
762 static void set_call_type(struct phonebook_contact *contact,
763                                 const char *datetime, const char *is_sent,
764                                 const char *is_answered)
765 {
766         gboolean sent, answered;
767
768         if (g_strcmp0(datetime, "NOTACALL") == 0) {
769                 contact->calltype = CALL_TYPE_NOT_A_CALL;
770                 return;
771         }
772
773         sent = g_str_equal(is_sent, "true");
774         answered = g_str_equal(is_answered, "true");
775
776         if (sent == FALSE) {
777                 if (answered == FALSE)
778                         contact->calltype = CALL_TYPE_MISSED;
779                 else
780                         contact->calltype = CALL_TYPE_INCOMING;
781         } else
782                 contact->calltype = CALL_TYPE_OUTGOING;
783
784         /* Tracker gives time in the ISO 8601 format, UTC time */
785         contact->datetime = iso8601_utc_to_localtime(datetime);
786 }
787
788 static gboolean contact_matches(struct contact_data *c_data, const char *id,
789                                                         const char *datetime)
790 {
791         char *localtime;
792         int cmp_ret;
793
794         if (g_strcmp0(c_data->id, id) != 0)
795                 return FALSE;
796
797         /* id is equal and not call history entry => contact matches */
798         if (c_data->contact->calltype == CALL_TYPE_NOT_A_CALL)
799                 return TRUE;
800
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);
804         g_free(localtime);
805
806         return (cmp_ret == 0) ? TRUE : FALSE;
807 }
808
809 static struct phonebook_contact *find_contact(GSList *contacts, const char *id,
810                                                         const char *datetime)
811 {
812         GSList *l;
813
814         for (l = contacts; l; l = l->next) {
815                 struct contact_data *c_data = l->data;
816
817                 if (contact_matches(c_data, id, datetime))
818                         return c_data->contact;
819         }
820
821         return NULL;
822 }
823
824 static struct phonebook_field *find_field(GSList *fields, const char *value,
825                                                                 int type)
826 {
827         GSList *l;
828
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
832                  * are equal */
833                 if (g_strcmp0(field->text, value) == 0 && field->type == type)
834                         return field;
835         }
836
837         return NULL;
838 }
839
840 static void add_phone_number(struct phonebook_contact *contact,
841                                                 const char *phone, int type)
842 {
843         struct phonebook_field *number;
844
845         if (phone == NULL || strlen(phone) == 0)
846                 return;
847
848         /* Not adding number if there is already added with the same value */
849         if (find_field(contact->numbers, phone, type))
850                 return;
851
852         number = g_new0(struct phonebook_field, 1);
853         number->text = g_strdup(phone);
854         number->type = type;
855
856         contact->numbers = g_slist_append(contact->numbers, number);
857 }
858
859 static void add_email(struct phonebook_contact *contact, const char *address,
860                                                                 int type)
861 {
862         struct phonebook_field *email;
863
864         if (address == NULL || strlen(address) == 0)
865                 return;
866
867         /* Not adding email if there is already added with the same value */
868         if (find_field(contact->emails, address, type))
869                 return;
870
871         email = g_new0(struct phonebook_field, 1);
872         email->text = g_strdup(address);
873         email->type = type;
874
875         contact->emails = g_slist_append(contact->emails, email);
876 }
877
878 static gboolean addr_matches(struct phonebook_addr *a, struct phonebook_addr *b)
879 {
880         GSList *la, *lb;
881
882         if (a->type != b->type)
883                 return FALSE;
884
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;
889
890                 if (g_strcmp0(field_a, field_b) != 0)
891                         return FALSE;
892         }
893
894         return TRUE;
895 }
896
897 /* generates phonebook_addr struct from tracker address data string. */
898 static struct phonebook_addr *gen_addr(const char *address, int type)
899 {
900         struct phonebook_addr *addr;
901         GSList *fields = NULL;
902         char **addr_parts;
903         int i;
904
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)
910                 return NULL;
911
912         addr_parts = g_strsplit(address, ADDR_DELIM, ADDR_FIELD_AMOUNT);
913
914         for (i = 0; i < ADDR_FIELD_AMOUNT; ++i)
915                 fields = g_slist_append(fields, g_strdup(addr_parts[i]));
916
917         g_strfreev(addr_parts);
918
919         addr = g_new0(struct phonebook_addr, 1);
920         addr->fields = fields;
921         addr->type = type;
922
923         return addr;
924 }
925
926 static void add_address(struct phonebook_contact *contact,
927                                         const char *address, int type)
928 {
929         struct phonebook_addr *addr;
930         GSList *l;
931
932         addr = gen_addr(address, type);
933         if (addr == NULL)
934                 return;
935
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;
942
943                 if (addr_matches(tmp, addr)) {
944                         phonebook_addr_free(addr);
945                         return;
946                 }
947         }
948
949         contact->addresses = g_slist_append(contact->addresses, addr);
950 }
951
952 static void add_url(struct phonebook_contact *contact, const char *url_val,
953                                                                 int type)
954 {
955         struct phonebook_field *url;
956
957         if (url_val == NULL || strlen(url_val) == 0)
958                 return;
959
960         /* Not adding url if there is already added with the same value */
961         if (find_field(contact->urls, url_val, type))
962                 return;
963
964         url = g_new0(struct phonebook_field, 1);
965
966         url->text = g_strdup(url_val);
967         url->type = type;
968
969         contact->urls = g_slist_append(contact->urls, url);
970 }
971
972 static GString *gen_vcards(GSList *contacts,
973                                         const struct apparam_field *params)
974 {
975         GSList *l;
976         GString *vcards;
977
978         vcards = g_string_new(NULL);
979
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);
985         }
986
987         return vcards;
988 }
989
990 static int pull_contacts_size(const char **reply, int num_fields,
991                                                         void *user_data)
992 {
993         struct phonebook_data *data = user_data;
994
995         if (num_fields < 0) {
996                 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
997                 return -EINTR;
998         }
999
1000         if (reply != NULL) {
1001                 data->index = atoi(reply[0]);
1002                 return 0;
1003         }
1004
1005         data->cb(NULL, 0, data->index, data->newmissedcalls, TRUE,
1006                                                         data->user_data);
1007
1008         return 0;
1009         /*
1010          * phonebook_data is freed in phonebook_req_finalize. Useful in
1011          * cases when call is terminated.
1012          */
1013 }
1014
1015 static void add_affiliation(char **field, const char *value)
1016 {
1017         if (strlen(*field) > 0 || value == NULL || strlen(value) == 0)
1018                 return;
1019
1020         g_free(*field);
1021
1022         *field = g_strdup(value);
1023 }
1024
1025 static void contact_init(struct phonebook_contact *contact,
1026                                                         const char **reply)
1027 {
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]);
1035                 else
1036                         contact->family = g_strdup(reply[COL_NICKNAME]);
1037         } else {
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]);
1043         }
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]);
1053
1054         set_call_type(contact, reply[COL_DATE], reply[COL_SENT],
1055                                                         reply[COL_ANSWERED]);
1056 }
1057
1058 static enum phonebook_number_type get_phone_type(const char *affilation)
1059 {
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;
1064
1065         return TEL_TYPE_OTHER;
1066 }
1067
1068 static void add_aff_number(struct phonebook_contact *contact,
1069                                 const char *pnumber, const char *aff_type)
1070 {
1071         char **num_parts;
1072         char *type, *number;
1073
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);
1078
1079         if (!num_parts)
1080                 return;
1081
1082         if (num_parts[0])
1083                 type = num_parts[0];
1084         else
1085                 goto failed;
1086
1087         if (num_parts[1])
1088                 number = num_parts[1];
1089         else
1090                 goto failed;
1091
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);
1096         else
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));
1100
1101 failed:
1102         g_strfreev(num_parts);
1103 }
1104
1105 static void contact_add_numbers(struct phonebook_contact *contact,
1106                                                         const char **reply)
1107 {
1108         char **aff_numbers;
1109         int i;
1110
1111         /* Filling phone numbers from contact's affilation */
1112         aff_numbers = g_strsplit(reply[COL_PHONE_AFF], MAIN_DELIM, MAX_FIELDS);
1113
1114         if (aff_numbers)
1115                 for (i = 0; aff_numbers[i]; ++i)
1116                         add_aff_number(contact, aff_numbers[i],
1117                                                         reply[COL_AFF_TYPE]);
1118
1119         g_strfreev(aff_numbers);
1120 }
1121
1122 static enum phonebook_field_type get_field_type(const char *affilation)
1123 {
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;
1128
1129         return FIELD_TYPE_OTHER;
1130 }
1131
1132 static void add_aff_field(struct phonebook_contact *contact,
1133                         const char *aff_email, add_field_t add_field_cb)
1134 {
1135         char **email_parts;
1136         char *type, *email;
1137
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);
1142
1143         if (!email_parts)
1144                 return;
1145
1146         if (email_parts[0])
1147                 email = email_parts[0];
1148         else
1149                 goto failed;
1150
1151         if (email_parts[1])
1152                 type = email_parts[1];
1153         else
1154                 goto failed;
1155
1156         add_field_cb(contact, email, get_field_type(type));
1157
1158 failed:
1159         g_strfreev(email_parts);
1160 }
1161
1162 static void contact_add_emails(struct phonebook_contact *contact,
1163                                                         const char **reply)
1164 {
1165         char **aff_emails;
1166         int i;
1167
1168         /* Emails from affilation */
1169         aff_emails = g_strsplit(reply[COL_EMAIL_AFF], MAIN_DELIM, MAX_FIELDS);
1170
1171         if (aff_emails)
1172                 for (i = 0; aff_emails[i] != NULL; ++i)
1173                         add_aff_field(contact, aff_emails[i], add_email);
1174
1175         g_strfreev(aff_emails);
1176 }
1177
1178 static void contact_add_addresses(struct phonebook_contact *contact,
1179                                                         const char **reply)
1180 {
1181         char **aff_addr;
1182         int i;
1183
1184         /* Addresses from affilation */
1185         aff_addr = g_strsplit(reply[COL_ADDR_AFF], MAIN_DELIM, MAX_FIELDS);
1186
1187         if (aff_addr)
1188                 for (i = 0; aff_addr[i] != NULL; ++i)
1189                         add_aff_field(contact, aff_addr[i], add_address);
1190
1191         g_strfreev(aff_addr);
1192 }
1193
1194 static void contact_add_urls(struct phonebook_contact *contact,
1195                                                         const char **reply)
1196 {
1197         char **aff_url;
1198         int i;
1199
1200         /* Addresses from affilation */
1201         aff_url = g_strsplit(reply[COL_URL], MAIN_DELIM, MAX_FIELDS);
1202
1203         if (aff_url)
1204                 for (i = 0; aff_url[i] != NULL; ++i)
1205                         add_aff_field(contact, aff_url[i], add_url);
1206
1207         g_strfreev(aff_url);
1208 }
1209
1210 static void contact_add_organization(struct phonebook_contact *contact,
1211                                                         const char **reply)
1212 {
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]);
1219 }
1220
1221 static void free_data_contacts(struct phonebook_data *data)
1222 {
1223         GSList *l;
1224
1225         /* freeing contacts */
1226         for (l = data->contacts; l; l = l->next) {
1227                 struct contact_data *c_data = l->data;
1228
1229                 g_free(c_data->id);
1230                 phonebook_contact_free(c_data->contact);
1231                 g_free(c_data);
1232         }
1233
1234         g_slist_free(data->contacts);
1235         data->contacts = NULL;
1236 }
1237
1238 static void send_pull_part(struct phonebook_data *data,
1239                         const struct apparam_field *params, gboolean lastpart)
1240 {
1241         GString *vcards;
1242
1243         DBG("");
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);
1247
1248         if (!lastpart)
1249                 free_data_contacts(data);
1250         g_string_free(vcards, TRUE);
1251 }
1252
1253 static int pull_contacts(const char **reply, int num_fields, void *user_data)
1254 {
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;
1259         int last_index, i;
1260         gboolean cdata_present = FALSE, part_sent = FALSE;
1261         static char *temp_id = NULL;
1262
1263         if (num_fields < 0) {
1264                 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
1265                 goto fail;
1266         }
1267
1268         DBG("reply %p", reply);
1269         data->tracker_index++;
1270
1271         if (reply == NULL)
1272                 goto done;
1273
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],
1277                                                         reply[COL_DATE]);
1278
1279         /* If contact is already created then adding only new phone numbers */
1280         if (contact) {
1281                 cdata_present = TRUE;
1282                 goto add_numbers;
1283         }
1284
1285         /* We are doing a PullvCardEntry, no need for those checks */
1286         if (data->vcardentry)
1287                 goto add_entry;
1288
1289         /* Last four fields are always present, ignoring them */
1290         for (i = 0; i < num_fields - 4; i++) {
1291                 if (reply[i][0] != '\0')
1292                         break;
1293         }
1294
1295         if (i == num_fields - 4 && !g_str_equal(reply[CONTACTS_ID_COL],
1296                                                 TRACKER_DEFAULT_CONTACT_ME))
1297                 return 0;
1298
1299         if (g_strcmp0(temp_id, reply[CONTACTS_ID_COL])) {
1300                 data->index++;
1301                 g_free(temp_id);
1302                 temp_id = g_strdup(reply[CONTACTS_ID_COL]);
1303
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++;
1308         }
1309
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);
1316
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*/
1321                 part_sent = TRUE;
1322         }
1323
1324         last_index = params->liststartoffset + params->maxlistcount;
1325
1326         if (data->index <= params->liststartoffset)
1327                 return 0;
1328
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");
1334                 goto done;
1335         }
1336
1337 add_entry:
1338         contact = g_new0(struct phonebook_contact, 1);
1339         contact_init(contact, reply);
1340
1341 add_numbers:
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);
1347
1348         DBG("contact %p", contact);
1349
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);
1357         }
1358
1359         if (part_sent)
1360                 return -EINTR;
1361
1362         return 0;
1363
1364 done:
1365         /* Processing is end, this is definitely last part of transmission
1366          * (marking lastpart as TRUE) */
1367         send_pull_part(data, params, TRUE);
1368
1369 fail:
1370         g_free(temp_id);
1371         temp_id = NULL;
1372
1373         return -EINTR;
1374         /*
1375          * phonebook_data is freed in phonebook_req_finalize. Useful in
1376          * cases when call is terminated.
1377          */
1378 }
1379
1380 static int add_to_cache(const char **reply, int num_fields, void *user_data)
1381 {
1382         struct phonebook_data *data = user_data;
1383         char *formatted;
1384         int i;
1385
1386         if (reply == NULL || num_fields < 0)
1387                 goto done;
1388
1389         /* the first element is the URI, always not empty */
1390         for (i = 1; i < num_fields; i++) {
1391                 if (reply[i][0] != '\0')
1392                         break;
1393         }
1394
1395         if (i == num_fields &&
1396                         !g_str_equal(reply[0], TRACKER_DEFAULT_CONTACT_ME))
1397                 return 0;
1398
1399         if (i == 7)
1400                 formatted = g_strdup(reply[7]);
1401         else if (i == 6)
1402                 formatted = g_strdup(reply[6]);
1403         else
1404                 formatted = g_strdup_printf("%s;%s;%s;%s;%s",
1405                                         reply[1], reply[2], reply[3], reply[4],
1406                                         reply[5]);
1407
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);
1412         else
1413                 data->entry_cb(reply[0], PHONEBOOK_INVALID_HANDLE, formatted,
1414                                         "", reply[6], data->user_data);
1415
1416         g_free(formatted);
1417
1418         return 0;
1419
1420 done:
1421         if (num_fields <= 0)
1422                 data->ready_cb(data->user_data);
1423
1424         return -EINTR;
1425         /*
1426          * phonebook_data is freed in phonebook_req_finalize. Useful in
1427          * cases when call is terminated.
1428          */
1429 }
1430
1431 int phonebook_init(void)
1432 {
1433         g_thread_init(NULL);
1434         g_type_init();
1435
1436         return 0;
1437 }
1438
1439 void phonebook_exit(void)
1440 {
1441 }
1442
1443 char *phonebook_set_folder(const char *current_folder, const char *new_folder,
1444                                                 uint8_t flags, int *err)
1445 {
1446         char *tmp1, *tmp2, *base, *path = NULL;
1447         gboolean root, child;
1448         int ret = 0;
1449         int len;
1450
1451         root = (g_strcmp0("/", current_folder) == 0);
1452         child = (new_folder && strlen(new_folder) != 0);
1453
1454         switch (flags) {
1455         case 0x02:
1456                 /* Go back to root */
1457                 if (!child) {
1458                         path = g_strdup("/");
1459                         goto done;
1460                 }
1461
1462                 path = g_build_filename(current_folder, new_folder, NULL);
1463                 break;
1464         case 0x03:
1465                 /* Go up 1 level */
1466                 if (root) {
1467                         /* Already root */
1468                         path = g_strdup("/");
1469                         goto done;
1470                 }
1471
1472                 /*
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.
1477                  */
1478                 tmp1 = g_path_get_basename(current_folder);
1479                 tmp2 = g_strrstr(current_folder, tmp1);
1480                 len = tmp2 - (current_folder + 1);
1481
1482                 g_free(tmp1);
1483
1484                 if (len == 0)
1485                         base = g_strdup("/");
1486                 else
1487                         base = g_strndup(current_folder, len);
1488
1489                 /* Return: one level only */
1490                 if (!child) {
1491                         path = base;
1492                         goto done;
1493                 }
1494
1495                 path = g_build_filename(base, new_folder, NULL);
1496                 g_free(base);
1497
1498                 break;
1499         default:
1500                 ret = -EBADR;
1501                 break;
1502         }
1503
1504 done:
1505         if (path && !folder_is_valid(path))
1506                 ret = -ENOENT;
1507
1508         if (ret < 0) {
1509                 g_free(path);
1510                 path = NULL;
1511         }
1512
1513         if (err)
1514                 *err = ret;
1515
1516         return path;
1517 }
1518
1519 static int pull_newmissedcalls(const char **reply, int num_fields,
1520                                                         void *user_data)
1521 {
1522         struct phonebook_data *data = user_data;
1523         reply_list_foreach_t pull_cb;
1524         int col_amount, err;
1525         const char *query;
1526         int nmissed;
1527
1528         if (num_fields < 0) {
1529                 data->cb(NULL, 0, num_fields, 0, TRUE, data->user_data);
1530
1531                 return -EINTR;
1532         }
1533
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);
1539
1540                 return 0;
1541         }
1542
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;
1547         } else {
1548                 query = name2query("/telecom/mch.vcf");
1549                 col_amount = PULL_QUERY_COL_AMOUNT;
1550                 pull_cb = pull_contacts;
1551         }
1552
1553         err = query_tracker(query, col_amount, pull_cb, data);
1554         if (err < 0) {
1555                 data->cb(NULL, 0, err, 0, TRUE, data->user_data);
1556
1557                 return -EINTR;
1558         }
1559
1560         return 0;
1561 }
1562
1563 void phonebook_req_finalize(void *request)
1564 {
1565         struct phonebook_data *data = request;
1566
1567         DBG("");
1568
1569         if (!data)
1570                 return;
1571
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);
1576         }
1577
1578         free_data_contacts(data);
1579         g_free(data->req_name);
1580         g_free(data);
1581 }
1582
1583 void *phonebook_pull(const char *name, const struct apparam_field *params,
1584                                 phonebook_cb cb, void *user_data, int *err)
1585 {
1586         struct phonebook_data *data;
1587
1588         DBG("name %s", name);
1589
1590         data = g_new0(struct phonebook_data, 1);
1591         data->params = params;
1592         data->user_data = user_data;
1593         data->cb = cb;
1594         data->req_name = g_strdup(name);
1595
1596         if (err)
1597                 *err = 0;
1598
1599         return data;
1600 }
1601
1602 int phonebook_pull_read(void *request)
1603 {
1604         struct phonebook_data *data = request;
1605         reply_list_foreach_t pull_cb;
1606         const char *query;
1607         char *offset_query;
1608         int col_amount;
1609         int ret;
1610
1611         if (!data)
1612                 return -ENOENT;
1613
1614         data->newmissedcalls = 0;
1615
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;
1628         } else {
1629                 query = name2query(data->req_name);
1630                 col_amount = PULL_QUERY_COL_AMOUNT;
1631                 pull_cb = pull_contacts;
1632         }
1633
1634         if (query == NULL)
1635                 return -ENOENT;
1636
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);
1644
1645                 g_free(offset_query);
1646
1647                 return ret;
1648         }
1649
1650         return query_tracker(query, col_amount, pull_cb, data);
1651 }
1652
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)
1656 {
1657         struct phonebook_data *data;
1658         char *query;
1659         int ret;
1660
1661         DBG("folder %s id %s", folder, id);
1662
1663         data = g_new0(struct phonebook_data, 1);
1664         data->user_data = user_data;
1665         data->params = params;
1666         data->cb = cb;
1667         data->vcardentry = TRUE;
1668
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);
1675         else
1676                 query = g_strdup_printf(CONTACTS_OTHER_QUERY_FROM_URI,
1677                                                                 id, id, id);
1678
1679         ret = query_tracker(query, PULL_QUERY_COL_AMOUNT, pull_contacts, data);
1680         if (err)
1681                 *err = ret;
1682
1683         g_free(query);
1684
1685         return data;
1686 }
1687
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)
1690 {
1691         struct phonebook_data *data;
1692         const char *query;
1693         int ret;
1694
1695         DBG("name %s", name);
1696
1697         query = folder2query(name);
1698         if (query == NULL) {
1699                 if (err)
1700                         *err = -ENOENT;
1701                 return NULL;
1702         }
1703
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;
1708
1709         ret = query_tracker(query, 8, add_to_cache, data);
1710         if (err)
1711                 *err = ret;
1712
1713         return data;
1714 }