15 #define OBEX_PREFIX "org.bluez.obex"
17 #define OBEX_CLIENT_IFACE OBEX_PREFIX ".Client1"
18 #define OBEX_PHONEBOOK_IFACE OBEX_PREFIX ".PhonebookAccess1"
19 #define OBEX_TRANSFER_IFACE OBEX_PREFIX ".Transfer1"
21 // check for stalled active transfer (in seconds)
22 #define CHECK_STALLED_TRANSFER_TIMEOUT 120
24 /*! \class PhoneD::SyncPBData
25 * A Class to provide a storage for Queued synchronization requests.
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.
35 SyncPBData(const char *location, const char *phonebook, unsigned long count = 0)
37 this->location = location;
38 this->phonebook = phonebook;
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). */
48 mSelectedRemoteDevice(""),
54 mContactsOrder.clear();
56 mCallHistoryOrder.clear();
61 removeSession(false); // remove session if it's active
64 void Obex::createSession(const char *bt_address) {
67 // remove existing session if exists
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);
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);
86 g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
93 G_DBUS_CALL_FLAGS_NONE,
96 Obex::asyncCreateSessionReadyCallback,
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);
104 LoggerE("Failed to cast object: Obex");
110 reply = g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL), result, &err);
112 ctx->createSessionFailed(err?err->message:"Invalid reply from 'CreateSession'");
118 const char *session = NULL;
119 g_variant_get(reply, "(o)", &session);
120 LoggerD("Created session: " << session);
122 // make a copy of object path, since it will be destroyed when the reply is unref-ed
124 ctx->mSession = strdup(session);
125 ctx->createSessionDone(ctx->mSession);
128 ctx->createSessionFailed("Failed to get 'session' from the 'CreateSession' reply");
130 g_variant_unref(reply);
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);
139 LoggerE("No session to execute operation on");
144 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
147 OBEX_PHONEBOOK_IFACE,
149 g_variant_new("(ss)", location, phonebook), // floating variants are consumed
151 G_DBUS_CALL_FLAGS_NONE,
156 LoggerE("Failed to select phonebook " << location << "/" << phonebook << ": " << err->message);
164 void Obex::removeSession(bool notify) {
165 if(!mSession) // there isn't active session to be removed
168 LoggerD("Removing session:" << mSession);
170 // delete/unref individual contacts
171 for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
172 EContact *contact = (*it).second;
174 // TODO: delete also all its attribs?
175 g_object_unref(contact);
179 mContactsOrder.clear();
181 // delete/unref individual cll history entries
182 for(auto it=mCallHistory.begin(); it!=mCallHistory.end(); ++it) {
183 EContact *item = (*it).second;
185 // TODO: delete also all its attribs?
186 g_object_unref(item);
189 mCallHistory.clear();
190 mCallHistoryOrder.clear();
193 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
198 g_variant_new("(o)", mSession), // floating variants are consumed
200 G_DBUS_CALL_FLAGS_NONE,
205 LoggerE("Failed to remove session " << mSession << ": " << err->message);
213 // clear sync queue, since all data/requests in the queue are not valid anymore
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();
240 LoggerD("Synchronization done");
241 pbSynchronizationDone();
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
251 void Obex::clearSyncQueue() {
253 if(mActiveTransfer) {
255 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
262 G_DBUS_CALL_FLAGS_NONE,
267 LoggerE("Failed to 'Cancel' active Transfer: " << err->message);
271 LoggerD("Active transfer 'Cancel'ed");
275 for(unsigned int i=0; i<mSyncQueue.size(); i++) {
276 delete mSyncQueue.at(i);
281 void Obex::setSelectedRemoteDevice(std::string &btAddress) {
282 mSelectedRemoteDevice = btAddress;
285 //DBUS: object, dict PullAll(string targetfile, dict filters)
286 Obex::Error Obex::pullAll(const char *type, unsigned long count) {
290 LoggerD("Invalid input argument(s)");
291 return OBEX_ERR_INVALID_ARGUMENTS;
295 LoggerE("No session to execute operation on");
296 initiateNextSyncRequest();
297 return OBEX_ERR_INVALID_SESSION;
303 GVariant *filters[8];
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);
313 // "Offset" -> Offset of the first item, default is 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);
321 GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), filters, nfilters);
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);
329 reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
332 OBEX_PHONEBOOK_IFACE,
336 G_DBUS_CALL_FLAGS_NONE,
342 LoggerE("Failed to 'PullAll': " << err->message);
343 initiateNextSyncRequest();
345 return OBEX_ERR_DBUS_ERROR;
349 LoggerE("Reply from call 'PullAll' is NULL");
350 initiateNextSyncRequest();
351 return OBEX_ERR_DBUS_INVALID_REPLY;
354 const char *transfer = NULL;
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));
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);
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);
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);
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);
392 g_variant_unref(reply);
394 return OBEX_ERR_NONE;
397 gboolean Obex::checkStalledTransfer(gpointer user_data) {
398 CtxCbData *data = static_cast<CtxCbData*>(user_data);
400 return G_SOURCE_REMOVE; // single shot timeout
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
408 if(ctx->mActiveTransfer && !strcmp(ctx->mActiveTransfer, transfer)) {
409 LoggerD("The active transfer is Stalled");
410 ctx->clearSyncQueue();
411 ctx->transferStalled();
415 return G_SOURCE_REMOVE; // single shot timeout
418 Obex::Error Obex::syncContacts(unsigned long count) {
421 LoggerD("Session not created, you have to call createSession before calling any method");
422 return OBEX_ERR_INVALID_SESSION;
424 mSyncQueue.push_back(new SyncPBData("INT", "pb", count));
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();
442 return OBEX_ERR_NONE;
445 void Obex::getContactByPhoneNumber(const char *phoneNumber, std::string &contact) {
447 // return empty JSON contact, in case phoneNumber is invalid
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);
461 g_list_free(phoneNumbersList);
465 // if the contact is not found, return empty JSON contact
469 void Obex::getJsonContacts(std::string& contacts, unsigned long count) {
474 // if count == 0, ie. return all contacts
475 count = (count>0 && count<mContactsOrder.size())?count:mContactsOrder.size();
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
484 parseEContactToJsonTizenContact(item, contact);
492 void Obex::parseEContactToJsonTizenContact(EContact *econtact, std::string &contact) {
493 const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
495 if(!econtact || !uid) {
496 contact = "{}"; // empty contact
502 contact += "\"uid\":\"";
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\":\"";
514 // DON'T free the list yet, it will be used later in the function
515 //if(phoneNumbersList)
516 // g_list_free(phoneNumbersList);
518 // addressBookId: not parsed
520 // lastUpdated: not parsed
522 // isFavorite: not parsed
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, "")) {
532 contact += "\"firstName\":\"";
533 contact += firstName;
536 if(lastName && strcmp(lastName, "")) {
538 contact += "\"lastName\":\"";
540 contact += ",\"lastName\":\"";
545 if(displayName && strcmp(displayName, "")) {
547 contact += "\"displayName\":\"";
549 contact += ",\"displayName\":\"";
551 contact += displayName;
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);
566 firstAddress = false;
567 contact += "\"isDefault\":\"false\"";
568 if(address->country && strcmp(address->country,"")) {
569 contact += ",\"country\":\"";
570 contact += address->country;
573 if(address->region && strcmp(address->region,"")) {
574 contact += ",\"region\":\"";
575 contact += address->region;
578 if(address->locality && strcmp(address->locality,"")) {
579 contact += ",\"city\":\"";
580 contact += address->locality;
583 if(address->street && strcmp(address->street,"")) {
584 contact += ",\"streetAddress\":\"";
585 contact += address->street;
588 if(address->code && strcmp(address->code,"")) {
589 contact += ",\"postalCode\":\"";
590 contact += address->code;
593 contact += ",\"types\":[\"";
594 contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
598 e_contact_address_free(address);
604 EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_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\":\"";
617 e_contact_photo_free(photo);
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, "")) {
626 contact += "{\"number\":\"";
628 contact += ",{\"number\":\"";
630 contact += phoneNumber;
633 phoneNumbersList = phoneNumbersList->next;
636 // now we can free the list
638 g_list_free(phoneNumbersList);
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, "")) {
647 contact += "{\"email\":\"";
649 contact += ",{\"email\":\"";
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
662 // birthday: not parsed
664 // anniversaries: not parsed
666 // organizations: not parsed
672 // ringtoneURI: not parsed
674 // groupIds: not parsed
679 Obex::Error Obex::syncCallHistory(unsigned long count) {
682 LoggerD("Session not created, you have to call createSession before calling any method");
683 return OBEX_ERR_INVALID_SESSION;
685 mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
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();
703 return OBEX_ERR_NONE;
706 void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
711 // if count == 0, ie. return all calls
712 count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
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
721 parseEContactToJsonTizenCallHistoryEntry(item, call);
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
738 call += "\"uid\":\"";
742 // type: not parsing - use some DEFAULT value, eg. "TEL"
743 call += "\"type\":\"TEL\",";
745 // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
746 call += "\"features\":[\"VOICECALL\"],";
749 call += "\"remoteParties\":[";
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
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\":\"";
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;
780 // duration: not parsing - use 0 as default value
781 call += "\"duration\":\"0\",";
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";
792 void Obex::handleSignal(GDBusConnection *connection,
794 const gchar *object_path,
795 const gchar *interface_name,
796 const gchar *signal_name,
797 GVariant *parameters,
800 LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
802 CtxCbData *data = static_cast<CtxCbData*>(user_data);
804 LoggerE("Failed to cast object: CtxCbData");
807 Obex *ctx = static_cast<Obex*>(data->ctx);
809 LoggerE("Failed to cast object: Obex");
813 if(!strcmp(signal_name, "PropertiesChanged"))
815 char *objPath = NULL;
816 GVariantIter* iter, *iter2;
818 g_variant_get(parameters, "(sa{sv}as)", &objPath, &iter, &iter2);
825 while(g_variant_iter_next(iter, "{sv}", &prop, &var))
827 if(!strcmp(prop, "Status"))
830 g_variant_get(var, "s", &status_str);
831 //LoggerD("Status is: " << status_str);
833 if(!strcmp(status_str, "complete"))
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);
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
844 ctx->initiateNextSyncRequest();
848 g_variant_unref(var);
853 LoggerD("No objectPath found. Exiting.");
855 g_variant_iter_free(iter);
856 g_variant_iter_free(iter2);
861 void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
864 if(!filePath || !type || !origin) {
865 LoggerE("Invalid argument(s)");
869 if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
870 LoggerD("Received VCards don't belong to currently selected device - IGNORING");
874 std::map<std::string, EContact*> *items = NULL;
875 std::vector<std::string> *order = NULL;
876 if(!strcmp(type, "pb")) { // Contacts
878 order = &mContactsOrder;
880 else if(!strcmp(type, "cch")) { // CallHistory
881 items = &mCallHistory;
882 order = &mCallHistoryOrder;
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;
891 // process VCards one-by-one
892 std::ifstream file(filePath);
894 for(std::string line; getline(file, line);)
896 //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
897 line.replace(line.find("\r"), 1, "\n");
899 if(line.find("BEGIN:VCARD") == 0) {
900 vcard = line; // start collecting new VCard
902 else if(line.find("END:VCARD") == 0) {
905 // start processing VCard
906 //printf("%s\n", vcard.c_str());
908 EContact *item = e_contact_new_from_vcard(vcard.c_str());
910 LoggerD("Failed to create EContact from vcard");
914 // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
916 // failed to create UID from EContact
917 // won't add the entry to the list - UID used as a key to the map
920 const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
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);
929 if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
931 const guchar *data = e_contact_photo_get_inlined (photo, &length);
932 //uid is used as a file name
934 snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
935 FILE *fd = fopen(fileName,"wb");
937 LoggerD("Unable to store contact photo: " << fileName);
940 LoggerD("Saving contact photo: " << fileName);
941 size_t written = fwrite(data, sizeof(guchar), length, 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);
948 photo = e_contact_photo_new();
950 photo->type = E_CONTACT_PHOTO_TYPE_URI;
951 //e_contact_photo_set_mime_type(photo, "");
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);
960 e_contact_photo_free(photo);
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;
968 order->push_back(uid);
970 order->insert(order->begin(), uid); // push at the front
971 if(!strcmp(type, "cch")) { // notify only for CallHistory
973 parseEContactToJsonTizenCallHistoryEntry(item, entry);
974 callHistoryEntryAdded(entry);
978 // the item already exists in the list, unref the item,
979 // since we loose any reference to it
980 g_object_unref(item);
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
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
993 else if(line.find("UID") == 0) {
994 // exclude UID as we are creating own UID
995 // exclude = do not copy it to vcard
997 else if(line.find("X-IRMC-CALL-DATETIME") == 0) {
998 size_t index1 = line.find( "TYPE=" ) + 5;
1000 // vcard format doesn't contain "TYPE=" prefix
1001 // use ";" as the index instead
1002 index1 = line.find( ";" ) + 1;
1004 size_t index2 = line.find( ":", index1 ) + 1;
1006 std::string note = line.substr (index1, index2-index1-1);
1007 std::string rev = line.substr (index2, line.length()-index2);
1009 vcard += "NOTE:" + note + "\n";
1010 vcard += "REV:" + rev; // '\n' is taken from 'line'
1018 // notify listener about Contacts/CallHistory being changed/synchronized
1020 if(!strcmp(type, "pb")) // Contacts
1022 else if(!strcmp(type, "cch")) // CallHistory
1023 callHistoryChanged();
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);
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?
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);
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);
1050 snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
1051 givenName?givenName:"",
1052 familyName?familyName:"",
1053 call_rev?call_rev:"");
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);
1060 if(phoneNumbersList)
1061 g_list_free(phoneNumbersList);