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 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);
562 contact += "\"isDefault\":\"false\"";
563 if(address->country && strcmp(address->country,"")) {
564 contact += ",\"country\":\"";
565 contact += address->country;
568 if(address->region && strcmp(address->region,"")) {
569 contact += ",\"region\":\"";
570 contact += address->region;
573 if(address->locality && strcmp(address->locality,"")) {
574 contact += ",\"city\":\"";
575 contact += address->locality;
578 if(address->street && strcmp(address->street,"")) {
579 contact += ",\"streetAddress\":\"";
580 contact += address->street;
583 if(address->code && strcmp(address->code,"")) {
584 contact += ",\"postalCode\":\"";
585 contact += address->code;
588 contact += ",\"types\":[\"";
589 contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
593 e_contact_address_free(address);
599 EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_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\":\"";
612 e_contact_photo_free(photo);
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, "")) {
621 contact += "{\"number\":\"";
623 contact += ",{\"number\":\"";
625 contact += phoneNumber;
628 phoneNumbersList = phoneNumbersList->next;
631 // now we can free the list
633 g_list_free(phoneNumbersList);
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, "")) {
642 contact += "{\"email\":\"";
644 contact += ",{\"email\":\"";
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
657 // birthday: not parsed
659 // anniversaries: not parsed
661 // organizations: not parsed
667 // ringtoneURI: not parsed
669 // groupIds: not parsed
674 Obex::Error Obex::syncCallHistory(unsigned long count) {
677 LoggerD("Session not created, you have to call createSession before calling any method");
678 return OBEX_ERR_INVALID_SESSION;
680 mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
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();
698 return OBEX_ERR_NONE;
701 void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
706 // if count == 0, ie. return all calls
707 count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
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
716 parseEContactToJsonTizenCallHistoryEntry(item, call);
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
733 call += "\"uid\":\"";
737 // type: not parsing - use some DEFAULT value, eg. "TEL"
738 call += "\"type\":\"TEL\",";
740 // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
741 call += "\"features\":[\"VOICECALL\"],";
744 call += "\"remoteParties\":[";
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
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\":\"";
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;
775 // duration: not parsing - use 0 as default value
776 call += "\"duration\":\"0\",";
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";
787 void Obex::handleSignal(GDBusConnection *connection,
789 const gchar *object_path,
790 const gchar *interface_name,
791 const gchar *signal_name,
792 GVariant *parameters,
795 LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
797 CtxCbData *data = static_cast<CtxCbData*>(user_data);
799 LoggerE("Failed to cast object: CtxCbData");
802 Obex *ctx = static_cast<Obex*>(data->ctx);
804 LoggerE("Failed to cast object: Obex");
808 if(!strcmp(signal_name, "PropertiesChanged"))
810 char *objPath = NULL;
811 GVariantIter* iter, iter2;
813 g_variant_get(parameters, "(sa{sv}as)", &objPath, &iter, &iter2);
820 while(g_variant_iter_next(iter, "{sv}", &prop, &var))
822 if(!strcmp(prop, "Status"))
825 g_variant_get(var, "s", &status_str);
826 //LoggerD("Status is: " << status_str);
828 if(!strcmp(status_str, "complete"))
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);
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
839 ctx->initiateNextSyncRequest();
846 LoggerD("No objectPath found. Exiting.");
851 void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
854 if(!filePath || !type || !origin) {
855 LoggerE("Invalid argument(s)");
859 if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
860 LoggerD("Received VCards don't belong to currently selected device - IGNORING");
864 std::map<std::string, EContact*> *items = NULL;
865 std::vector<std::string> *order = NULL;
866 if(!strcmp(type, "pb")) { // Contacts
868 order = &mContactsOrder;
870 else if(!strcmp(type, "cch")) { // CallHistory
871 items = &mCallHistory;
872 order = &mCallHistoryOrder;
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;
881 // process VCards one-by-one
882 std::ifstream file(filePath);
884 for(std::string line; getline(file, line);)
886 //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
887 line.replace(line.find("\r"), 1, "\n");
889 if(line.find("BEGIN:VCARD") == 0) {
890 vcard = line; // start collecting new VCard
892 else if(line.find("END:VCARD") == 0) {
895 // start processing VCard
896 //printf("%s\n", vcard.c_str());
898 EContact *item = e_contact_new_from_vcard(vcard.c_str());
900 LoggerD("Failed to create EContact from vcard");
904 // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
906 // failed to create UID from EContact
907 // won't add the entry to the list - UID used as a key to the map
910 const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
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);
919 if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
921 const guchar *data = e_contact_photo_get_inlined (photo, &length);
922 //uid is used as a file name
924 snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
925 FILE *fd = fopen(fileName,"wb");
927 LoggerD("Unable to store contact photo: " << fileName);
930 LoggerD("Saving contact photo: " << fileName);
931 size_t written = fwrite(data, sizeof(guchar), length, 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);
938 photo = e_contact_photo_new();
940 photo->type = E_CONTACT_PHOTO_TYPE_URI;
941 //e_contact_photo_set_mime_type(photo, "");
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);
950 e_contact_photo_free(photo);
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;
958 order->push_back(uid);
960 order->insert(order->begin(), uid); // push at the front
961 if(!strcmp(type, "cch")) { // notify only for CallHistory
963 parseEContactToJsonTizenCallHistoryEntry(item, entry);
964 callHistoryEntryAdded(entry);
968 // the item already exists in the list, unref the item,
969 // since we loose any reference to it
970 g_object_unref(item);
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
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
983 else if(line.find("UID") == 0) {
984 // exclude UID as we are creating own UID
985 // exclude = do not copy it to vcard
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;
991 std::string note = line.substr (index1, index2-index1-1);
992 std::string rev = line.substr (index2, line.length()-index2);
994 vcard += "NOTE:" + note + "\n";
995 vcard += "REV:" + rev; // '\n' is taken from 'line'
1003 // notify listener about Contacts/CallHistory being changed/synchronized
1005 if(!strcmp(type, "pb")) // Contacts
1007 else if(!strcmp(type, "cch")) // CallHistory
1008 callHistoryChanged();
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);
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?
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);
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);
1035 snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
1036 givenName?givenName:"",
1037 familyName?familyName:"",
1038 call_rev?call_rev:"");
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);
1045 if(phoneNumbersList)
1046 g_list_free(phoneNumbersList);