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