Changing dbus calls to use bluez 5 API calls. This allows the phone app to connect...
[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        contact += ",\"addresses\":[";
558        for(int id=E_CONTACT_ADDRESS_HOME; id<=E_CONTACT_ADDRESS_OTHER; id++) {
559            EContactAddress *address = (EContactAddress*)e_contact_get(econtact, (EContactField)id);
560            if(address) {
561                contact += "{";
562                contact += "\"isDefault\":\"false\"";
563                if(address->country && strcmp(address->country,"")) {
564                    contact += ",\"country\":\"";
565                    contact += address->country;
566                    contact += "\"";
567                }
568                if(address->region && strcmp(address->region,"")) {
569                    contact += ",\"region\":\"";
570                    contact += address->region;
571                    contact += "\"";
572                }
573                if(address->locality && strcmp(address->locality,"")) {
574                    contact += ",\"city\":\"";
575                    contact += address->locality;
576                    contact += "\"";
577                }
578                if(address->street && strcmp(address->street,"")) {
579                    contact += ",\"streetAddress\":\"";
580                    contact += address->street;
581                    contact += "\"";
582                }
583                if(address->code && strcmp(address->code,"")) {
584                    contact += ",\"postalCode\":\"";
585                    contact += address->code;
586                    contact += "\"";
587                }
588                contact += ",\"types\":[\"";
589                contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
590                contact += "\"]";
591                contact += "}";
592
593                e_contact_address_free(address);
594            }
595        }
596        contact += "]";
597
598        // photoURI:
599        EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_PHOTO);
600        if(photo) {
601            // we should have only URI type of contact photo, ... see processVCards() method
602            if(E_CONTACT_PHOTO_TYPE_URI == photo->type) {
603                 const char *uri = e_contact_photo_get_uri(photo);
604                 if(uri && strcmp(uri, "")) {
605                     //LoggerD("photoURI = " << uri);
606                     contact += ",\"photoURI\":\"";
607                     contact += uri;
608                     contact += "\"";
609                 }
610            }
611        }
612        e_contact_photo_free(photo);
613
614        // phoneNumbers
615        contact += ",\"phoneNumbers\":[";
616        bool firstNumber = true;
617        while(phoneNumbersList && phoneNumbersList->data) {
618            const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:"";
619            if(phoneNumber && strcmp(phoneNumber, "")) {
620                if(firstNumber)
621                    contact += "{\"number\":\"";
622                else
623                    contact += ",{\"number\":\"";
624                firstNumber = false;
625                contact += phoneNumber;
626                contact += "\"}";
627            }
628            phoneNumbersList = phoneNumbersList->next;
629        }
630        contact += "]";
631        // now we can free the list
632        if(phoneNumbersList)
633            g_list_free(phoneNumbersList);
634
635        // emails:
636        contact += ",\"emails\":[";
637        bool firstEmail = true;
638        for(int id=E_CONTACT_FIRST_EMAIL_ID; id<=E_CONTACT_LAST_EMAIL_ID; id++) {
639            const char *email = (const char*)e_contact_get_const(econtact, (EContactField)id);
640            if(email && strcmp(email, "")) {
641                if(firstEmail)
642                    contact += "{\"email\":\"";
643                else
644                    contact += ",{\"email\":\"";
645
646                contact += email;
647                contact += "\"";
648                contact += ",\"isDefault\":\"false\""; // TODO: ?use 'firstEmail' value to set the first e-mail address as default?
649                contact += ",\"types\":[\"WORK\"]"; // just some default value
650
651                firstEmail = false;
652                contact += "}";
653            }
654        }
655        contact += "]";
656
657        // birthday: not parsed
658
659        // anniversaries: not parsed
660
661        // organizations: not parsed
662
663        // notes: not parsed
664
665        // urls: not parsed
666
667        // ringtoneURI: not parsed
668
669        // groupIds: not parsed
670
671        contact += "}";
672 }
673
674 Obex::Error Obex::syncCallHistory(unsigned long count) {
675     LoggerD("entered");
676     if(!mSession) {
677         LoggerD("Session not created, you have to call createSession before calling any method");
678         return OBEX_ERR_INVALID_SESSION;
679     }
680     mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
681
682     // if the size is one, that means that there has not been
683     // synchronization on-going and therefore we can initiate
684     // synchronization from here, otherwise it will be initiated
685     // once the current one will have finished
686     if(mSyncQueue.size() == 1) {
687         SyncPBData *sync = mSyncQueue.front();
688         LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
689         if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
690             if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
691                 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
692                 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
693                 initiateNextSyncRequest();
694             }
695         }
696     }
697
698     return OBEX_ERR_NONE;
699 }
700
701 void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
702     LoggerD("entered");
703
704     calls = "[";
705
706     // if count == 0, ie. return all calls
707     count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
708
709     for(unsigned int i = 0; i<count; ++i) { // get 'count' latest calls, ie. 'count' first from the list
710         EContact *item = mCallHistory[mCallHistoryOrder.at(i)];
711         if(item) { // make sure, that the item exists
712             //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?
713             if(calls.compare("[")) // exclude ',' for the first entry
714                 calls += ",";
715             std::string call;
716             parseEContactToJsonTizenCallHistoryEntry(item, call);
717             calls += call;
718         }
719     }
720
721     calls += "]";
722 }
723
724 void Obex::parseEContactToJsonTizenCallHistoryEntry(EContact *econtact, std::string &call) {
725        const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
726        if(!econtact || !uid) {
727            call = "{}"; // empty call history entry
728            return;
729        }
730
731        // uid:
732        call = "{";
733        call += "\"uid\":\"";
734        call += uid;
735        call += "\",";
736
737        // type: not parsing - use some DEFAULT value, eg. "TEL"
738        call += "\"type\":\"TEL\",";
739
740        // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
741        call += "\"features\":[\"VOICECALL\"],";
742
743        // remoteParties
744        call += "\"remoteParties\":[";
745        call += "{";
746        call += "\"personId\":\"";
747        GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
748        const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:""; // phoneNumber is used as personId - first number from the list is used
749        call += personId;
750        call += "\"";
751        if(phoneNumbersList)
752            g_list_free(phoneNumbersList);
753        const char *fullName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
754        if(fullName && strcmp(fullName, "")) {
755            call += ",\"remoteParty\":\"";
756            call += fullName;
757            call += "\"";
758        }
759        call += "}";
760        call += "],";
761
762        // startTime
763        const char *startTime = (const char*)e_contact_get_const(econtact, E_CONTACT_REV); // 'REV' holds call date/time
764        if(startTime && strcmp(startTime, "")) {
765            std::string startTimeStr = startTime;
766            startTimeStr.insert(13,":");
767            startTimeStr.insert(11,":");
768            startTimeStr.insert(6,"-");
769            startTimeStr.insert(4,"-");
770            call += "\"startTime\":\"";
771            call += startTimeStr;
772            call += "\",";
773        }
774
775        // duration: not parsing - use 0 as default value
776        call += "\"duration\":\"0\",";
777
778        //  direction:
779        call += "\"direction\":\"";
780        const char *direction = (const char*)e_contact_get_const(econtact, E_CONTACT_NOTE); // 'NOTE' holds direction of the call
781        call += direction?direction:(char*)"UNDEFINED";
782        call += "\"";
783
784        call += "}";
785 }
786
787 void Obex::handleSignal(GDBusConnection       *connection,
788                          const gchar           *sender,
789                          const gchar           *object_path,
790                          const gchar           *interface_name,
791                          const gchar           *signal_name,
792                          GVariant              *parameters,
793                          gpointer               user_data)
794 {
795     LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
796
797     CtxCbData *data = static_cast<CtxCbData*>(user_data);
798     if(!data) {
799         LoggerE("Failed to cast object: CtxCbData");
800         return;
801     }
802     Obex *ctx = static_cast<Obex*>(data->ctx);
803     if(!ctx) {
804         LoggerE("Failed to cast object: Obex");
805         return;
806     }
807
808     if(!strcmp(signal_name, "PropertiesChanged"))
809     {
810                 char *objPath = NULL;
811                 GVariantIter* iter, iter2;
812
813                 g_variant_get(parameters, "(sa{sv}as)", &objPath, &iter, &iter2);
814
815                 if(objPath)
816                 {
817                         GVariant* var;
818                         char *prop = NULL;
819
820                         while(g_variant_iter_next(iter, "{sv}", &prop, &var))
821                         {
822                                 if(!strcmp(prop, "Status"))
823                                 {
824                                         char *status_str=0;
825                                         g_variant_get(var, "s", &status_str);
826                                         //LoggerD("Status is: " << status_str);
827
828                                         if(!strcmp(status_str, "complete"))
829                                         {
830                                                 const char *path = static_cast<const char *>(data->data1);
831                                                 const char *type = static_cast<const char *>(data->data2);
832                                                 const char *origin = static_cast<const char *>(data->cb);
833                                                 ctx->processVCards(path, type, origin);
834
835
836                                                 if(data->data1) free(data->data1); // path - to the file containing received VCards
837                                                 if(data->cb) free(data->cb);       // origin - MAC address of selected remote device
838                                                 delete data;
839                                                 ctx->initiateNextSyncRequest();
840                                         }
841                                 }
842                         }
843                 }
844                 else
845                 {
846                         LoggerD("No objectPath found. Exiting.");
847                 }
848     }
849 }
850
851 void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
852     LoggerD("entered");
853
854     if(!filePath || !type || !origin) {
855         LoggerE("Invalid argument(s)");
856         return;
857     }
858
859     if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
860         LoggerD("Received VCards don't belong to currently selected device - IGNORING");
861         return;
862     }
863
864     std::map<std::string, EContact*> *items = NULL;
865     std::vector<std::string> *order = NULL;
866     if(!strcmp(type, "pb")) { // Contacts
867         items = &mContacts;
868         order = &mContactsOrder;
869     }
870     else if(!strcmp(type, "cch")) { // CallHistory
871         items = &mCallHistory;
872         order = &mCallHistoryOrder;
873     }
874     // if the size of items map is 0, ie. that the received
875     // VCards are from first sync request and they should
876     // be added to the map (uid order vector) in the order they
877     // are processed (push_back), otherwise they should be
878     // inserted at the front (push_front)
879     bool firstData = items->size() == 0 ? true : false;
880
881     // process VCards one-by-one
882     std::ifstream file(filePath);
883     std::string vcard;
884     for(std::string line; getline(file, line);)
885     {
886         //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
887         line.replace(line.find("\r"), 1, "\n");
888
889         if(line.find("BEGIN:VCARD") == 0) {
890             vcard = line; // start collecting new VCard
891         }
892         else if(line.find("END:VCARD") == 0) {
893             vcard += line;
894
895             // start processing VCard
896             //printf("%s\n", vcard.c_str());
897
898             EContact *item = e_contact_new_from_vcard(vcard.c_str());
899             if(!item) {
900                 LoggerD("Failed to create EContact from vcard");
901                 continue;
902             }
903
904             // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
905             if(!makeUid(item)) {
906                 // failed to create UID from EContact
907                 // won't add the entry to the list - UID used as a key to the map
908                 continue;
909             }
910             const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
911
912             // check if item has photo and it's INLINED type
913             // if so, change it to URI type, since the data are in binary form
914             // and as such can't be processed in JSON directly
915             // to avoid yet another conversion to eg. BASE64 format, save the
916             // contact photo in /tmp and use the URI to reference the photo instead
917             EContactPhoto *photo = (EContactPhoto*)e_contact_get(item, E_CONTACT_PHOTO);
918             if(photo) {
919                 if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
920                     gsize length = 0;
921                     const guchar *data = e_contact_photo_get_inlined (photo, &length);
922                     //uid is used as a file name
923                     char fileName[128];
924                     snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
925                     FILE *fd = fopen(fileName,"wb");
926                     if(!fd) {
927                         LoggerD("Unable to store contact photo: " << fileName);
928                     }
929                     else {
930                         LoggerD("Saving contact photo: " << fileName);
931                         size_t written = fwrite(data, sizeof(guchar), length, fd);
932                         fclose(fd);
933                         if(written == length) {
934                             // contact photo has been successfully saved
935                             // change photo attribute from INLINED to URI
936                             e_contact_photo_free(photo);
937                             photo = NULL;
938                             photo = e_contact_photo_new();
939                             if(photo) {
940                                 photo->type = E_CONTACT_PHOTO_TYPE_URI;
941                                 //e_contact_photo_set_mime_type(photo, "");
942                                 char uri[128];
943                                 snprintf(uri, sizeof(uri), "file://%s", fileName);
944                                 e_contact_photo_set_uri(photo, uri);
945                                 e_contact_set(item, E_CONTACT_PHOTO, photo);
946                             }
947                         }
948                     }
949                 }
950                 e_contact_photo_free(photo);
951             }
952
953             // check if an item with the given UID exists in the list
954             if((*items)[uid] == NULL) {
955                 //LoggerD("NEW ITEM: " << uid);
956                 (*items)[uid] = item;
957                 if(firstData)
958                     order->push_back(uid);
959                 else
960                     order->insert(order->begin(), uid); // push at the front
961                 if(!strcmp(type, "cch")) { // notify only for CallHistory
962                     std::string entry;
963                     parseEContactToJsonTizenCallHistoryEntry(item, entry);
964                     callHistoryEntryAdded(entry);
965                 }
966             }
967             else {
968                 // the item already exists in the list, unref the item,
969                 // since we loose any reference to it
970                 g_object_unref(item);
971             }
972         }
973         else {
974             // the current implementation of EContact doesn't support
975             // X-IRMC-CALL-DATETIME field, so as a workaround we use
976             // two separate fields instead: E_CONTACT_NOTE
977             //                              E_CONTACT_REV
978             if((line.find("NOTE") == 0) || (line.find("REV") == 0)) {
979                 // exclude NOTE and REV as we are using it to store
980                 // X-IRMC-CALL-DATETIME attribute
981                 // exclude = do not copy it to vcard
982             }
983             else if(line.find("UID") == 0) {
984                 // exclude UID as we are creating own UID
985                 // exclude = do not copy it to vcard
986             }
987             else if(line.find("X-IRMC-CALL-DATETIME") == 0) {
988                 size_t index1 = line.find( "TYPE=" ) + 5;
989                 size_t index2 = line.find( ":", index1 ) + 1;
990
991                 std::string note = line.substr (index1, index2-index1-1);
992                 std::string rev = line.substr (index2, line.length()-index2);
993
994                 vcard += "NOTE:" + note + "\n";
995                 vcard += "REV:" + rev; // '\n' is taken from 'line'
996             }
997             else {
998                 vcard += line;
999             }
1000         }
1001     }
1002
1003     // notify listener about Contacts/CallHistory being changed/synchronized
1004     if(type) {
1005         if(!strcmp(type, "pb")) // Contacts
1006             contactsChanged();
1007         else if(!strcmp(type, "cch")) // CallHistory
1008             callHistoryChanged();
1009     }
1010 }
1011
1012 bool Obex::makeUid(EContact *entry) {
1013     // use combination of phone number, given/family name and the modification date
1014     const char *_uid = (const char*)e_contact_get_const(entry, E_CONTACT_UID);
1015     if(_uid) {
1016         // we shouldn't get here, since E_CONTACT_UID is filtered-out in processVCards() method
1017         // does "e_contact_set" frees the memory if the field already exists?
1018         return false;
1019     }
1020     GList *phoneNumbersList = (GList*)e_contact_get(entry, E_CONTACT_TEL);
1021     const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data) ? (const char*)phoneNumbersList->data : NULL;
1022     const char *givenName = (const char*)e_contact_get_const(entry, E_CONTACT_GIVEN_NAME);
1023     const char *familyName = (const char*)e_contact_get_const(entry, E_CONTACT_FAMILY_NAME);
1024     const char *call_rev = (const char*)e_contact_get_const(entry, E_CONTACT_REV);
1025
1026     if((!phoneNumber || !phoneNumber[0]) && (!givenName || !givenName[0]) && (!familyName || !familyName[0]) && (!call_rev || !call_rev[0])) {
1027         // uid is used as a key to the map
1028         LoggerD("Invalid EContact entry - not adding to the list");
1029         if(phoneNumbersList)
1030             g_list_free(phoneNumbersList);
1031         return false;
1032     }
1033
1034     char uid[128];
1035     snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
1036                                               givenName?givenName:"",
1037                                               familyName?familyName:"",
1038                                               call_rev?call_rev:"");
1039
1040     // TODO: check
1041     // does "e_contact_set" make a copy of value, or
1042     // do we need make a copy of the value on the HEAP?
1043     e_contact_set(entry, E_CONTACT_UID, uid);
1044
1045     if(phoneNumbersList)
1046         g_list_free(phoneNumbersList);
1047
1048     return true;
1049 }
1050
1051 } // PhoneD
1052