Initial checkin for phone daemon for the HTML5 UI
[profile/ivi/phoned.git] / src / obex.cpp
1
2 #include "obex.h"
3 #include "utils.h"
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <gio/gio.h>
9 #include <fstream>
10
11 #include "Logger.h"
12
13 namespace PhoneD {
14
15 #define OBEX_PREFIX                        "org.bluez.obex"
16
17 #define OBEX_CLIENT_SERVICE                OBEX_PREFIX ".client"
18 #define OBEX_CLIENT_IFACE                  OBEX_PREFIX ".Client"
19 #define OBEX_PHONEBOOK_IFACE               OBEX_PREFIX ".PhonebookAccess"
20 #define OBEX_TRANSFER_IFACE                OBEX_PREFIX ".Transfer"
21
22 // check for stalled active transfer (in seconds)
23 #define CHECK_STALLED_TRANSFER_TIMEOUT     120
24
25 /*! \class PhoneD::SyncPBData
26  * A Class to provide a storage for Queued synchronization requests.
27  */
28 class SyncPBData {
29     public:
30         /**
31          * A default constructor which allows to specify object data in the construction phase.
32          * @param[in] location Location of phonebook data, see SyncPBData::location.
33          * @param[in] phonebook Phonebook data identification, see SyncPBData::phonebook.
34          * @param[in] count Number of latest entries to be synchronized (the default is 0), see SyncPBData::count.
35          */
36         SyncPBData(const char *location, const char *phonebook, unsigned long count = 0)
37         {
38             this->location = location;
39             this->phonebook = phonebook;
40             this->count = count;
41         }
42     public:
43         const char *location;   /*!< Location of phonebook data: "INT", "SIM1", "SIM2". */
44         const char *phonebook;  /*!< Phonebook data identification: "pb", "ich", "och", "mch", "cch". */
45         unsigned long count;    /*!< Number of latest entries to be synchronized (0 means to request all). */
46 };
47
48 Obex::Obex() :
49     mSelectedRemoteDevice(""),
50     mSession(NULL),
51     mActiveTransfer(NULL)
52 {
53     LoggerD("entered");
54     mContacts.clear();
55     mContactsOrder.clear();
56     mCallHistory.clear();
57     mCallHistoryOrder.clear();
58 }
59
60 Obex::~Obex() {
61     LoggerD("entered");
62     removeSession(false); // remove session if it's active
63 }
64
65 void Obex::createSession(const char *bt_address) {
66     LoggerD("entered");
67
68     // remove existing session if exists
69     removeSession(false);
70
71     GVariant *args[8];
72     int nargs = 0;
73
74     // add dict entry for "PBAP" target
75     GVariant * key = g_variant_new_string("Target");
76     GVariant * str = g_variant_new_string("PBAP");
77     GVariant * var = g_variant_new_variant(str);
78     args[nargs++] = g_variant_new_dict_entry(key, var);
79     GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), args, nargs);
80
81     // build the parameters variant
82     GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
83     g_variant_builder_add_value(builder, g_variant_new("s", bt_address));
84     g_variant_builder_add_value(builder, array);
85     GVariant *parameters = g_variant_builder_end(builder);
86
87     g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
88                             OBEX_CLIENT_SERVICE,
89                             "/",
90                             OBEX_CLIENT_IFACE,
91                             "CreateSession",
92                             parameters,
93                             NULL,
94                             G_DBUS_CALL_FLAGS_NONE,
95                             -1,
96                             NULL,
97                             Obex::asyncCreateSessionReadyCallback,
98                             this);
99 }
100
101 // callback for async call of "CreateSession" method
102 void Obex::asyncCreateSessionReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
103     Obex *ctx = static_cast<Obex*>(user_data);
104     if(!ctx) {
105         LoggerE("Failed to cast object: Obex");
106         return;
107     }
108
109     GError *err = NULL;
110     GVariant *reply;
111     reply = g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL), result, &err);
112     if(err || !reply) {
113         ctx->createSessionFailed(err?err->message:"Invalid reply from 'CreateSession'");
114         if(err)
115             g_error_free(err);
116         return;
117     }
118
119     const char *session = NULL;
120     g_variant_get(reply, "(o)", &session);
121     LoggerD("Created session: " << session);
122
123     // make a copy of object path, since it will be destroyed when the reply is unref-ed
124     if(session) {
125         ctx->mSession = strdup(session);
126         ctx->createSessionDone(ctx->mSession);
127     }
128     else
129         ctx->createSessionFailed("Failed to get 'session' from the 'CreateSession' reply");
130
131     g_variant_unref(reply);
132 }
133
134 // location:  "INT", "SIM1", "SIM2"
135 // phonebook: "pb", "ich", "och", "mch", "cch"
136 bool Obex::select(const char *location, const char *phonebook) {
137     LoggerD("Selecting phonebook: " << location << "/" << phonebook);
138
139     if(!mSession) {
140         LoggerE("No session to execute operation on");
141         return false;
142     }
143
144     GError *err = NULL;
145     g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
146                                  OBEX_CLIENT_SERVICE,
147                                  mSession,
148                                  OBEX_PHONEBOOK_IFACE,
149                                  "Select",
150                                  g_variant_new("(ss)", location, phonebook), // floating variants are consumed
151                                  NULL,
152                                  G_DBUS_CALL_FLAGS_NONE,
153                                  -1,
154                                  NULL,
155                                  &err);
156     if(err) {
157         LoggerE("Failed to select phonebook " << location << "/" << phonebook << ": " << err->message);
158         g_error_free(err);
159         return false;
160     }
161
162     return true;
163 }
164
165 void Obex::removeSession(bool notify) {
166     if(!mSession) // there isn't active session to be removed
167         return;
168
169     LoggerD("Removing session:" << mSession);
170
171     // delete/unref individual contacts
172     for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
173         EContact *contact = (*it).second;
174         if(contact) {
175             // TODO: delete also all its attribs?
176             g_object_unref(contact);
177         }
178     }
179     mContacts.clear();
180     mContactsOrder.clear();
181
182     // delete/unref individual cll history entries
183     for(auto it=mCallHistory.begin(); it!=mCallHistory.end(); ++it) {
184         EContact *item = (*it).second;
185         if(item) {
186             // TODO: delete also all its attribs?
187             g_object_unref(item);
188         }
189     }
190     mCallHistory.clear();
191     mCallHistoryOrder.clear();
192
193     GError *err = NULL;
194     g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
195                                  OBEX_CLIENT_SERVICE,
196                                  "/",
197                                  OBEX_CLIENT_IFACE,
198                                  "RemoveSession",
199                                  g_variant_new("(o)", mSession), // floating variants are consumed
200                                  NULL,
201                                  G_DBUS_CALL_FLAGS_NONE,
202                                  -1,
203                                  NULL,
204                                  &err);
205     if(err) {
206         LoggerE("Failed to remove session " << mSession << ": " << err->message);
207         g_error_free(err);
208     }
209
210     free(mSession);
211     mSession = NULL;
212
213
214     // clear sync queue, since all data/requests in the queue are not valid anymore
215     clearSyncQueue();
216
217     if(notify)
218         removeSessionDone();
219 }
220
221 // this method should be called once the individual sync operation has finished
222 // the sync operation that is on-going is at the top of the queue
223 void Obex::initiateNextSyncRequest() {
224     // remove the actual sync operation, which has just finished
225     if(!mSyncQueue.empty()) {
226         delete mSyncQueue.front();
227         mSyncQueue.pop_front();
228         if(!mSyncQueue.empty()) {
229             // there is another sync request in the queue
230             SyncPBData *sync = mSyncQueue.front();
231             LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook);
232             if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
233                 if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
234                     // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
235                     // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
236                     initiateNextSyncRequest();
237                 }
238             }
239         }
240         else {
241             LoggerD("Synchronization done");
242             pbSynchronizationDone();
243         }
244     }
245     else {
246         // we should never get here, but just in case
247         // inform the user that the sync has finished
248         // TODO: emit the signal here
249     }
250 }
251
252 void Obex::clearSyncQueue() {
253     /*
254     if(mActiveTransfer) {
255         GError *err = NULL;
256         g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
257                                      OBEX_CLIENT_SERVICE,
258                                      mActiveTransfer,
259                                      OBEX_TRANSFER_IFACE,
260                                      "Cancel",
261                                      NULL,
262                                      NULL,
263                                      G_DBUS_CALL_FLAGS_NONE,
264                                      -1,
265                                      NULL,
266                                      &err);
267         if(err) {
268             LoggerE("Failed to 'Cancel' active Transfer: " << err->message);
269             g_error_free(err);
270             return;
271         }
272         LoggerD("Active transfer 'Cancel'ed");
273     }
274     */
275
276     for(unsigned int i=0; i<mSyncQueue.size(); i++) {
277         delete mSyncQueue.at(i);
278     }
279     mSyncQueue.clear();
280 }
281
282 void Obex::setSelectedRemoteDevice(std::string &btAddress) {
283     mSelectedRemoteDevice = btAddress;
284 }
285
286 //DBUS: object, dict PullAll(string targetfile, dict filters)
287 Obex::Error Obex::pullAll(const char *type, unsigned long count) {
288     LoggerD("entered");
289
290     if(!type) {
291         LoggerD("Invalid input argument(s)");
292         return OBEX_ERR_INVALID_ARGUMENTS;
293     }
294
295     if(!mSession) {
296         LoggerE("No session to execute operation on");
297         initiateNextSyncRequest();
298         return OBEX_ERR_INVALID_SESSION;
299     }
300
301     GError *err = NULL;
302     GVariant *reply;
303
304     GVariant *filters[8];
305     int nfilters = 0;
306
307     GVariant *name, *str, *var;
308     // "Format" -> "vcard30"
309     name = g_variant_new_string("Format");
310     str = g_variant_new_string("vcard30");
311     var = g_variant_new_variant(str);
312     filters[nfilters++] = g_variant_new_dict_entry(name, var);
313
314     // "Offset" -> Offset of the first item, default is 0
315     if(count > 0) {
316         name = g_variant_new_string("MaxCount");
317         str = g_variant_new_uint16(count);
318         var = g_variant_new_variant(str);
319         filters[nfilters++] = g_variant_new_dict_entry(name, var);
320     }
321
322     GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), filters, nfilters);
323
324     // build the parameters variant
325     GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
326     g_variant_builder_add_value(builder, g_variant_new("s", "")); // target file name will be automatically calculated
327     g_variant_builder_add_value(builder, array);
328     GVariant *parameters = g_variant_builder_end(builder);
329
330     reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
331                                          OBEX_CLIENT_SERVICE,
332                                          mSession,
333                                          OBEX_PHONEBOOK_IFACE,
334                                          "PullAll",
335                                          parameters,
336                                          NULL,
337                                          G_DBUS_CALL_FLAGS_NONE,
338                                          -1,
339                                          NULL,
340                                          &err);
341
342     if(err) {
343         LoggerE("Failed to 'PullAll': " << err->message);
344         initiateNextSyncRequest();
345         g_error_free(err);
346         return OBEX_ERR_DBUS_ERROR;
347     }
348
349     if(!reply) {
350         LoggerE("Reply from call 'PullAll' is NULL");
351         initiateNextSyncRequest();
352         return OBEX_ERR_DBUS_INVALID_REPLY;
353     }
354
355     const char *transfer = NULL;
356     GVariantIter *iter;
357     g_variant_get(reply, "((oa{sv}))", &transfer, &iter);
358     LoggerD("transfer path = " << transfer);
359     mActiveTransfer = strdup(transfer);
360     g_timeout_add(CHECK_STALLED_TRANSFER_TIMEOUT*1000,
361                   Obex::checkStalledTransfer,
362                   new CtxCbData(this, NULL, strdup(transfer), NULL));
363
364     // let's abuse 'cb' field from CtxCbData to store selected remote device's MAC
365     CtxCbData *data = new CtxCbData(this, strdup(mSelectedRemoteDevice.c_str()), NULL, (void*)type);
366     // we can set signal listener for "Error" from here,
367     // since we don't need to know file path of stored transfer data
368     Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
369                              OBEX_TRANSFER_IFACE, transfer, "Error",
370                              Obex::handleSignal, data); //TODO: think of using 'error' callback here,
371                                                         //      or if error occurs, returns empty NULL string
372
373     const char *key;
374     GVariant *value;
375     while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
376         if(!strcmp(key, "Size")) { // "Size"
377             //guint64 val = g_variant_get_uint64(value);
378             //LoggerD(key << " = " << val);
379         }
380         else { // "Name", "Filename"
381             //LoggerD(key << " = " << g_variant_get_string(value, NULL));
382             if(!strcmp(key, "Filename")) {
383                 const char *fileName = g_variant_get_string(value, NULL);
384                 if(fileName) {
385                     LoggerD("Saving pulled data/VCards into: " << fileName);
386                     // we call subscribe for "Complete" signal here, since we need to know path of stored file
387                     // !!! what if signal comes before we subscribe for it? ... CAN IT? ... signals from DBUS
388                     // should be executed in the thread the registration was made from, ie. this method has to
389                     // to be completed first
390                     data->data1 = strdup(fileName);
391                     Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
392                                              OBEX_TRANSFER_IFACE, transfer, "Complete",
393                                              Obex::handleSignal, data);
394                 }
395             }
396         }
397     }
398
399     g_variant_unref(reply);
400
401     return OBEX_ERR_NONE;
402 }
403
404 gboolean Obex::checkStalledTransfer(gpointer user_data) {
405     CtxCbData *data = static_cast<CtxCbData*>(user_data);
406     if(!data)
407         return G_SOURCE_REMOVE; // single shot timeout
408
409     Obex *ctx = static_cast<Obex*>(data->ctx);
410     char *transfer = static_cast<char*>(data->data1);
411     if(!ctx || !transfer) {
412         LoggerE("Failed to cast to Obex");
413         return G_SOURCE_REMOVE; // single shot timeout
414     }
415     if(ctx->mActiveTransfer && !strcmp(ctx->mActiveTransfer, transfer)) {
416         LoggerD("The active transfer is Stalled");
417         ctx->clearSyncQueue();
418         ctx->transferStalled();
419     }
420     free(transfer);
421     delete data;
422     return G_SOURCE_REMOVE; // single shot timeout
423 }
424
425 Obex::Error Obex::syncContacts(unsigned long count) {
426     LoggerD("entered");
427     if(!mSession) {
428         LoggerD("Session not created, you have to call createSession before calling any method");
429         return OBEX_ERR_INVALID_SESSION;
430     }
431     mSyncQueue.push_back(new SyncPBData("INT", "pb", count));
432
433     // if the size is one, that means that there has not been
434     // synchronization on-going and therefore we can initiate
435     // synchronization from here, otherwise it will be initiated
436     // once the current one will have finished
437     if(mSyncQueue.size() == 1) {
438         SyncPBData *sync = mSyncQueue.front();
439         LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
440         if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
441             if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
442                 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
443                 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
444                 initiateNextSyncRequest();
445             }
446         }
447     }
448
449     return OBEX_ERR_NONE;
450 }
451
452 void Obex::getContactByPhoneNumber(const char *phoneNumber, std::string &contact) {
453     if(!phoneNumber) {
454         // return empty JSON contact, in case phoneNumber is invalid
455         contact = "{}";
456         return;
457     }
458
459     for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
460         GList *phoneNumbersList = (GList*)e_contact_get((*it).second, E_CONTACT_TEL);
461         if(phoneNumbersList) {
462             const char *phoneNumberToCheck = phoneNumbersList->data?(const char*)phoneNumbersList->data:NULL;
463             if(phoneNumberToCheck && !strcmp(phoneNumberToCheck, phoneNumber)) {
464                 parseEContactToJsonTizenContact((*it).second, contact);
465                 g_list_free(phoneNumbersList);
466                 return;
467             }
468             g_list_free(phoneNumbersList);
469         }
470     }
471
472     // if the contact is not found, return empty JSON contact
473     contact = "{}";
474 }
475
476 void Obex::getJsonContacts(std::string& contacts, unsigned long count) {
477     LoggerD("entered");
478
479     contacts = "[";
480
481     // if count == 0, ie. return all contacts
482     count = (count>0 && count<mContactsOrder.size())?count:mContactsOrder.size();
483
484     for(unsigned int i = 0; i<count; ++i) { // get 'count' latest contacts, ie. 'count' first from the list
485         EContact *item = mContacts[mContactsOrder.at(i)];
486         if(item) { // make sure, that the item exists
487             //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
488             if(contacts.compare("[")) // exclude ',' for the first entry
489                 contacts += ",";
490             std::string contact;
491             parseEContactToJsonTizenContact(item, contact);
492             contacts += contact;
493         }
494     }
495
496     contacts += "]";
497 }
498
499 void Obex::parseEContactToJsonTizenContact(EContact *econtact, std::string &contact) {
500        const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
501
502        if(!econtact || !uid) {
503            contact = "{}"; // empty contact
504            return;
505        }
506
507        // uid:
508        contact = "{";
509        contact += "\"uid\":\"";
510        contact += uid;
511        contact += "\"";
512
513        // personId:
514        GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
515        const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:NULL; // phoneNumber is used as personId - first number from the list is used
516        if(personId && strcmp(personId, "")) {
517            contact += ",\"personId\":\"";
518            contact += personId;
519            contact += "\"";
520        }
521        // DON'T free the list yet, it will be used later in the function
522        //if(phoneNumbersList)
523        //    g_list_free(phoneNumbersList);
524
525        // addressBookId: not parsed
526
527        // lastUpdated: not parsed
528
529        // isFavorite: not parsed
530
531        // name:
532        contact += ",\"name\":{";
533        const char *firstName = (const char*)e_contact_get_const(econtact, E_CONTACT_GIVEN_NAME);
534        const char *lastName = (const char*)e_contact_get_const(econtact, E_CONTACT_FAMILY_NAME);
535        const char *displayName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
536        bool firstAttr = true; // used to indicate whether separating comma should be used
537        if(firstName && strcmp(firstName, "")) {
538            firstAttr = false;
539            contact += "\"firstName\":\"";
540            contact += firstName;
541            contact += "\"";
542        }
543        if(lastName && strcmp(lastName, "")) {
544            if(firstAttr)
545                contact += "\"lastName\":\"";
546            else
547                contact += ",\"lastName\":\"";
548            firstAttr = false;
549            contact += lastName;
550            contact += "\"";
551        }
552        if(displayName && strcmp(displayName, "")) {
553            if(firstAttr)
554                contact += "\"displayName\":\"";
555            else
556                contact += ",\"displayName\":\"";
557            firstAttr = false;
558            contact += displayName;
559            contact += "\"";
560        }
561        contact += "}";
562
563        // addresses:
564        contact += ",\"addresses\":[";
565        for(int id=E_CONTACT_ADDRESS_HOME; id<=E_CONTACT_ADDRESS_OTHER; id++) {
566            EContactAddress *address = (EContactAddress*)e_contact_get(econtact, (EContactField)id);
567            if(address) {
568                contact += "{";
569                contact += "\"isDefault\":\"false\"";
570                if(address->country && strcmp(address->country,"")) {
571                    contact += ",\"country\":\"";
572                    contact += address->country;
573                    contact += "\"";
574                }
575                if(address->region && strcmp(address->region,"")) {
576                    contact += ",\"region\":\"";
577                    contact += address->region;
578                    contact += "\"";
579                }
580                if(address->locality && strcmp(address->locality,"")) {
581                    contact += ",\"city\":\"";
582                    contact += address->locality;
583                    contact += "\"";
584                }
585                if(address->street && strcmp(address->street,"")) {
586                    contact += ",\"streetAddress\":\"";
587                    contact += address->street;
588                    contact += "\"";
589                }
590                if(address->code && strcmp(address->code,"")) {
591                    contact += ",\"postalCode\":\"";
592                    contact += address->code;
593                    contact += "\"";
594                }
595                contact += ",\"types\":[\"";
596                contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
597                contact += "\"]";
598                contact += "}";
599
600                e_contact_address_free(address);
601            }
602        }
603        contact += "]";
604
605        // photoURI:
606        EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_PHOTO);
607        if(photo) {
608            // we should have only URI type of contact photo, ... see processVCards() method
609            if(E_CONTACT_PHOTO_TYPE_URI == photo->type) {
610                 const char *uri = e_contact_photo_get_uri(photo);
611                 if(uri && strcmp(uri, "")) {
612                     //LoggerD("photoURI = " << uri);
613                     contact += ",\"photoURI\":\"";
614                     contact += uri;
615                     contact += "\"";
616                 }
617            }
618        }
619        e_contact_photo_free(photo);
620
621        // phoneNumbers
622        contact += ",\"phoneNumbers\":[";
623        bool firstNumber = true;
624        while(phoneNumbersList && phoneNumbersList->data) {
625            const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:"";
626            if(phoneNumber && strcmp(phoneNumber, "")) {
627                if(firstNumber)
628                    contact += "{\"number\":\"";
629                else
630                    contact += ",{\"number\":\"";
631                firstNumber = false;
632                contact += phoneNumber;
633                contact += "\"}";
634            }
635            phoneNumbersList = phoneNumbersList->next;
636        }
637        contact += "]";
638        // now we can free the list
639        if(phoneNumbersList)
640            g_list_free(phoneNumbersList);
641
642        // emails:
643        contact += ",\"emails\":[";
644        bool firstEmail = true;
645        for(int id=E_CONTACT_FIRST_EMAIL_ID; id<=E_CONTACT_LAST_EMAIL_ID; id++) {
646            const char *email = (const char*)e_contact_get_const(econtact, (EContactField)id);
647            if(email && strcmp(email, "")) {
648                if(firstEmail)
649                    contact += "{\"email\":\"";
650                else
651                    contact += ",{\"email\":\"";
652
653                contact += email;
654                contact += "\"";
655                contact += ",\"isDefault\":\"false\""; // TODO: ?use 'firstEmail' value to set the first e-mail address as default?
656                contact += ",\"types\":[\"WORK\"]"; // just some default value
657
658                firstEmail = false;
659                contact += "}";
660            }
661        }
662        contact += "]";
663
664        // birthday: not parsed
665
666        // anniversaries: not parsed
667
668        // organizations: not parsed
669
670        // notes: not parsed
671
672        // urls: not parsed
673
674        // ringtoneURI: not parsed
675
676        // groupIds: not parsed
677
678        contact += "}";
679 }
680
681 Obex::Error Obex::syncCallHistory(unsigned long count) {
682     LoggerD("entered");
683     if(!mSession) {
684         LoggerD("Session not created, you have to call createSession before calling any method");
685         return OBEX_ERR_INVALID_SESSION;
686     }
687     mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
688
689     // if the size is one, that means that there has not been
690     // synchronization on-going and therefore we can initiate
691     // synchronization from here, otherwise it will be initiated
692     // once the current one will have finished
693     if(mSyncQueue.size() == 1) {
694         SyncPBData *sync = mSyncQueue.front();
695         LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
696         if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
697             if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
698                 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
699                 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
700                 initiateNextSyncRequest();
701             }
702         }
703     }
704
705     return OBEX_ERR_NONE;
706 }
707
708 void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
709     LoggerD("entered");
710
711     calls = "[";
712
713     // if count == 0, ie. return all calls
714     count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
715
716     for(unsigned int i = 0; i<count; ++i) { // get 'count' latest calls, ie. 'count' first from the list
717         EContact *item = mCallHistory[mCallHistoryOrder.at(i)];
718         if(item) { // make sure, that the item exists
719             //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
720             if(calls.compare("[")) // exclude ',' for the first entry
721                 calls += ",";
722             std::string call;
723             parseEContactToJsonTizenCallHistoryEntry(item, call);
724             calls += call;
725         }
726     }
727
728     calls += "]";
729 }
730
731 void Obex::parseEContactToJsonTizenCallHistoryEntry(EContact *econtact, std::string &call) {
732        const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
733        if(!econtact || !uid) {
734            call = "{}"; // empty call history entry
735            return;
736        }
737
738        // uid:
739        call = "{";
740        call += "\"uid\":\"";
741        call += uid;
742        call += "\",";
743
744        // type: not parsing - use some DEFAULT value, eg. "TEL"
745        call += "\"type\":\"TEL\",";
746
747        // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
748        call += "\"features\":[\"VOICECALL\"],";
749
750        // remoteParties
751        call += "\"remoteParties\":[";
752        call += "{";
753        call += "\"personId\":\"";
754        GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
755        const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:""; // phoneNumber is used as personId - first number from the list is used
756        call += personId;
757        call += "\"";
758        if(phoneNumbersList)
759            g_list_free(phoneNumbersList);
760        const char *fullName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
761        if(fullName && strcmp(fullName, "")) {
762            call += ",\"remoteParty\":\"";
763            call += fullName;
764            call += "\"";
765        }
766        call += "}";
767        call += "],";
768
769        // startTime
770        const char *startTime = (const char*)e_contact_get_const(econtact, E_CONTACT_REV); // 'REV' holds call date/time
771        if(startTime && strcmp(startTime, "")) {
772            std::string startTimeStr = startTime;
773            startTimeStr.insert(13,":");
774            startTimeStr.insert(11,":");
775            startTimeStr.insert(6,"-");
776            startTimeStr.insert(4,"-");
777            call += "\"startTime\":\"";
778            call += startTimeStr;
779            call += "\",";
780        }
781
782        // duration: not parsing - use 0 as default value
783        call += "\"duration\":\"0\",";
784
785        //  direction:
786        call += "\"direction\":\"";
787        const char *direction = (const char*)e_contact_get_const(econtact, E_CONTACT_NOTE); // 'NOTE' holds direction of the call
788        call += direction?direction:(char*)"UNDEFINED";
789        call += "\"";
790
791        call += "}";
792 }
793
794 void Obex::handleSignal(GDBusConnection       *connection,
795                          const gchar           *sender,
796                          const gchar           *object_path,
797                          const gchar           *interface_name,
798                          const gchar           *signal_name,
799                          GVariant              *parameters,
800                          gpointer               user_data)
801 {
802     LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
803
804     CtxCbData *data = static_cast<CtxCbData*>(user_data);
805     if(!data) {
806         LoggerE("Failed to cast object: CtxCbData");
807         return;
808     }
809     Obex *ctx = static_cast<Obex*>(data->ctx);
810     if(!ctx) {
811         LoggerE("Failed to cast object: Obex");
812         return;
813     }
814
815     if(!strcmp(interface_name, OBEX_TRANSFER_IFACE)) {
816         if(!strcmp(signal_name, "Complete") || !strcmp(signal_name, "Error")) {
817             Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
818                                         OBEX_TRANSFER_IFACE, object_path,
819                                         "Complete");
820             Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
821                                         OBEX_TRANSFER_IFACE, object_path,
822                                         "Error");
823             if(ctx->mActiveTransfer) {
824                 free(ctx->mActiveTransfer);
825                 ctx->mActiveTransfer = NULL;
826             }
827
828             if(!strcmp(signal_name, "Complete")) {
829                 const char *path = static_cast<const char *>(data->data1);
830                 const char *type = static_cast<const char *>(data->data2);
831                 const char *origin = static_cast<const char *>(data->cb);
832                 ctx->processVCards(path, type, origin);
833             }
834             else if(!strcmp(signal_name, "Error")) {
835                 // dont' have to do anything
836             }
837             if(data->data1) free(data->data1); // path - to the file containing received VCards
838             if(data->cb) free(data->cb);       // origin - MAC address of selected remote device
839             delete data;
840             ctx->initiateNextSyncRequest();
841         }
842     }
843 }
844
845 void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
846     LoggerD("entered");
847
848     if(!filePath || !type || !origin) {
849         LoggerE("Invalid argument(s)");
850         return;
851     }
852
853     if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
854         LoggerD("Received VCards don't belong to currently selected device - IGNORING");
855         return;
856     }
857
858     std::map<std::string, EContact*> *items = NULL;
859     std::vector<std::string> *order = NULL;
860     if(!strcmp(type, "pb")) { // Contacts
861         items = &mContacts;
862         order = &mContactsOrder;
863     }
864     else if(!strcmp(type, "cch")) { // CallHistory
865         items = &mCallHistory;
866         order = &mCallHistoryOrder;
867     }
868     // if the size of items map is 0, ie. that the received
869     // VCards are from first sync request and they should
870     // be added to the map (uid order vector) in the order they
871     // are processed (push_back), otherwise they should be
872     // inserted at the front (push_front)
873     bool firstData = items->size() == 0 ? true : false;
874
875     // process VCards one-by-one
876     std::ifstream file(filePath);
877     std::string vcard;
878     for(std::string line; getline(file, line);)
879     {
880         //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
881         line.replace(line.find("\r"), 1, "\n");
882
883         if(line.find("BEGIN:VCARD") == 0) {
884             vcard = line; // start collecting new VCard
885         }
886         else if(line.find("END:VCARD") == 0) {
887             vcard += line;
888
889             // start processing VCard
890             //printf("%s\n", vcard.c_str());
891
892             EContact *item = e_contact_new_from_vcard(vcard.c_str());
893             if(!item) {
894                 LoggerD("Failed to create EContact from vcard");
895                 continue;
896             }
897
898             // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
899             if(!makeUid(item)) {
900                 // failed to create UID from EContact
901                 // won't add the entry to the list - UID used as a key to the map
902                 continue;
903             }
904             const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
905
906             // check if item has photo and it's INLINED type
907             // if so, change it to URI type, since the data are in binary form
908             // and as such can't be processed in JSON directly
909             // to avoid yet another conversion to eg. BASE64 format, save the
910             // contact photo in /tmp and use the URI to reference the photo instead
911             EContactPhoto *photo = (EContactPhoto*)e_contact_get(item, E_CONTACT_PHOTO);
912             if(photo) {
913                 if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
914                     gsize length = 0;
915                     const guchar *data = e_contact_photo_get_inlined (photo, &length);
916                     //uid is used as a file name
917                     char fileName[128];
918                     snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
919                     FILE *fd = fopen(fileName,"wb");
920                     if(!fd) {
921                         LoggerD("Unable to store contact photo: " << fileName);
922                     }
923                     else {
924                         LoggerD("Saving contact photo: " << fileName);
925                         size_t written = fwrite(data, sizeof(guchar), length, fd);
926                         fclose(fd);
927                         if(written == length) {
928                             // contact photo has been successfully saved
929                             // change photo attribute from INLINED to URI
930                             e_contact_photo_free(photo);
931                             photo = NULL;
932                             photo = e_contact_photo_new();
933                             if(photo) {
934                                 photo->type = E_CONTACT_PHOTO_TYPE_URI;
935                                 //e_contact_photo_set_mime_type(photo, "");
936                                 char uri[128];
937                                 snprintf(uri, sizeof(uri), "file://%s", fileName);
938                                 e_contact_photo_set_uri(photo, uri);
939                                 e_contact_set(item, E_CONTACT_PHOTO, photo);
940                             }
941                         }
942                     }
943                 }
944                 e_contact_photo_free(photo);
945             }
946
947             // check if an item with the given UID exists in the list
948             if((*items)[uid] == NULL) {
949                 //LoggerD("NEW ITEM: " << uid);
950                 (*items)[uid] = item;
951                 if(firstData)
952                     order->push_back(uid);
953                 else
954                     order->insert(order->begin(), uid); // push at the front
955                 if(!strcmp(type, "cch")) { // notify only for CallHistory
956                     std::string entry;
957                     parseEContactToJsonTizenCallHistoryEntry(item, entry);
958                     callHistoryEntryAdded(entry);
959                 }
960             }
961             else {
962                 // the item already exists in the list, unref the item,
963                 // since we loose any reference to it
964                 g_object_unref(item);
965             }
966         }
967         else {
968             // the current implementation of EContact doesn't support
969             // X-IRMC-CALL-DATETIME field, so as a workaround we use
970             // two separate fields instead: E_CONTACT_NOTE
971             //                              E_CONTACT_REV
972             if((line.find("NOTE") == 0) || (line.find("REV") == 0)) {
973                 // exclude NOTE and REV as we are using it to store
974                 // X-IRMC-CALL-DATETIME attribute
975                 // exclude = do not copy it to vcard
976             }
977             else if(line.find("UID") == 0) {
978                 // exclude UID as we are creating own UID
979                 // exclude = do not copy it to vcard
980             }
981             else if(line.find("X-IRMC-CALL-DATETIME") == 0) {
982                 size_t index1 = line.find( "TYPE=" ) + 5;
983                 size_t index2 = line.find( ":", index1 ) + 1;
984
985                 std::string note = line.substr (index1, index2-index1-1);
986                 std::string rev = line.substr (index2, line.length()-index2);
987
988                 vcard += "NOTE:" + note + "\n";
989                 vcard += "REV:" + rev; // '\n' is taken from 'line'
990             }
991             else {
992                 vcard += line;
993             }
994         }
995     }
996
997     // notify listener about Contacts/CallHistory being changed/synchronized
998     if(type) {
999         if(!strcmp(type, "pb")) // Contacts
1000             contactsChanged();
1001         else if(!strcmp(type, "cch")) // CallHistory
1002             callHistoryChanged();
1003     }
1004 }
1005
1006 bool Obex::makeUid(EContact *entry) {
1007     // use combination of phone number, given/family name and the modification date
1008     const char *_uid = (const char*)e_contact_get_const(entry, E_CONTACT_UID);
1009     if(_uid) {
1010         // we shouldn't get here, since E_CONTACT_UID is filtered-out in processVCards() method
1011         // does "e_contact_set" frees the memory if the field already exists?
1012         return false;
1013     }
1014     GList *phoneNumbersList = (GList*)e_contact_get(entry, E_CONTACT_TEL);
1015     const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data) ? (const char*)phoneNumbersList->data : NULL;
1016     const char *givenName = (const char*)e_contact_get_const(entry, E_CONTACT_GIVEN_NAME);
1017     const char *familyName = (const char*)e_contact_get_const(entry, E_CONTACT_FAMILY_NAME);
1018     const char *call_rev = (const char*)e_contact_get_const(entry, E_CONTACT_REV);
1019
1020     if((!phoneNumber || !phoneNumber[0]) && (!givenName || !givenName[0]) && (!familyName || !familyName[0]) && (!call_rev || !call_rev[0])) {
1021         // uid is used as a key to the map
1022         LoggerD("Invalid EContact entry - not adding to the list");
1023         if(phoneNumbersList)
1024             g_list_free(phoneNumbersList);
1025         return false;
1026     }
1027
1028     char uid[128];
1029     snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
1030                                               givenName?givenName:"",
1031                                               familyName?familyName:"",
1032                                               call_rev?call_rev:"");
1033
1034     // TODO: check
1035     // does "e_contact_set" make a copy of value, or
1036     // do we need make a copy of the value on the HEAP?
1037     e_contact_set(entry, E_CONTACT_UID, uid);
1038
1039     if(phoneNumbersList)
1040         g_list_free(phoneNumbersList);
1041
1042     return true;
1043 }
1044
1045 } // PhoneD
1046