15 #define OBEX_PREFIX "org.bluez.obex"
17 #define OBEX_CLIENT_SERVICE OBEX_PREFIX ".client"
18 #define OBEX_CLIENT_IFACE OBEX_PREFIX ".Client"
19 #define OBEX_PHONEBOOK_IFACE OBEX_PREFIX ".PhonebookAccess"
20 #define OBEX_TRANSFER_IFACE OBEX_PREFIX ".Transfer"
22 // check for stalled active transfer (in seconds)
23 #define CHECK_STALLED_TRANSFER_TIMEOUT 120
25 /*! \class PhoneD::SyncPBData
26 * A Class to provide a storage for Queued synchronization requests.
31 * A default constructor which allows to specify object data in the construction phase.
32 * @param[in] location Location of phonebook data, see SyncPBData::location.
33 * @param[in] phonebook Phonebook data identification, see SyncPBData::phonebook.
34 * @param[in] count Number of latest entries to be synchronized (the default is 0), see SyncPBData::count.
36 SyncPBData(const char *location, const char *phonebook, unsigned long count = 0)
38 this->location = location;
39 this->phonebook = phonebook;
43 const char *location; /*!< Location of phonebook data: "INT", "SIM1", "SIM2". */
44 const char *phonebook; /*!< Phonebook data identification: "pb", "ich", "och", "mch", "cch". */
45 unsigned long count; /*!< Number of latest entries to be synchronized (0 means to request all). */
49 mSelectedRemoteDevice(""),
55 mContactsOrder.clear();
57 mCallHistoryOrder.clear();
62 removeSession(false); // remove session if it's active
65 void Obex::createSession(const char *bt_address) {
68 // remove existing session if exists
74 // add dict entry for "PBAP" target
75 GVariant * key = g_variant_new_string("Target");
76 GVariant * str = g_variant_new_string("PBAP");
77 GVariant * var = g_variant_new_variant(str);
78 args[nargs++] = g_variant_new_dict_entry(key, var);
79 GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), args, nargs);
81 // build the parameters variant
82 GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
83 g_variant_builder_add_value(builder, g_variant_new("s", bt_address));
84 g_variant_builder_add_value(builder, array);
85 GVariant *parameters = g_variant_builder_end(builder);
87 g_dbus_connection_call( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
94 G_DBUS_CALL_FLAGS_NONE,
97 Obex::asyncCreateSessionReadyCallback,
101 // callback for async call of "CreateSession" method
102 void Obex::asyncCreateSessionReadyCallback(GObject *source, GAsyncResult *result, gpointer user_data) {
103 Obex *ctx = static_cast<Obex*>(user_data);
105 LoggerE("Failed to cast object: Obex");
111 reply = g_dbus_connection_call_finish(g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL), result, &err);
113 ctx->createSessionFailed(err?err->message:"Invalid reply from 'CreateSession'");
119 const char *session = NULL;
120 g_variant_get(reply, "(o)", &session);
121 LoggerD("Created session: " << session);
123 // make a copy of object path, since it will be destroyed when the reply is unref-ed
125 ctx->mSession = strdup(session);
126 ctx->createSessionDone(ctx->mSession);
129 ctx->createSessionFailed("Failed to get 'session' from the 'CreateSession' reply");
131 g_variant_unref(reply);
134 // location: "INT", "SIM1", "SIM2"
135 // phonebook: "pb", "ich", "och", "mch", "cch"
136 bool Obex::select(const char *location, const char *phonebook) {
137 LoggerD("Selecting phonebook: " << location << "/" << phonebook);
140 LoggerE("No session to execute operation on");
145 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
148 OBEX_PHONEBOOK_IFACE,
150 g_variant_new("(ss)", location, phonebook), // floating variants are consumed
152 G_DBUS_CALL_FLAGS_NONE,
157 LoggerE("Failed to select phonebook " << location << "/" << phonebook << ": " << err->message);
165 void Obex::removeSession(bool notify) {
166 if(!mSession) // there isn't active session to be removed
169 LoggerD("Removing session:" << mSession);
171 // delete/unref individual contacts
172 for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
173 EContact *contact = (*it).second;
175 // TODO: delete also all its attribs?
176 g_object_unref(contact);
180 mContactsOrder.clear();
182 // delete/unref individual cll history entries
183 for(auto it=mCallHistory.begin(); it!=mCallHistory.end(); ++it) {
184 EContact *item = (*it).second;
186 // TODO: delete also all its attribs?
187 g_object_unref(item);
190 mCallHistory.clear();
191 mCallHistoryOrder.clear();
194 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
199 g_variant_new("(o)", mSession), // floating variants are consumed
201 G_DBUS_CALL_FLAGS_NONE,
206 LoggerE("Failed to remove session " << mSession << ": " << err->message);
214 // clear sync queue, since all data/requests in the queue are not valid anymore
221 // this method should be called once the individual sync operation has finished
222 // the sync operation that is on-going is at the top of the queue
223 void Obex::initiateNextSyncRequest() {
224 // remove the actual sync operation, which has just finished
225 if(!mSyncQueue.empty()) {
226 delete mSyncQueue.front();
227 mSyncQueue.pop_front();
228 if(!mSyncQueue.empty()) {
229 // there is another sync request in the queue
230 SyncPBData *sync = mSyncQueue.front();
231 LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook);
232 if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
233 if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
234 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
235 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
236 initiateNextSyncRequest();
241 LoggerD("Synchronization done");
242 pbSynchronizationDone();
246 // we should never get here, but just in case
247 // inform the user that the sync has finished
248 // TODO: emit the signal here
252 void Obex::clearSyncQueue() {
254 if(mActiveTransfer) {
256 g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
263 G_DBUS_CALL_FLAGS_NONE,
268 LoggerE("Failed to 'Cancel' active Transfer: " << err->message);
272 LoggerD("Active transfer 'Cancel'ed");
276 for(unsigned int i=0; i<mSyncQueue.size(); i++) {
277 delete mSyncQueue.at(i);
282 void Obex::setSelectedRemoteDevice(std::string &btAddress) {
283 mSelectedRemoteDevice = btAddress;
286 //DBUS: object, dict PullAll(string targetfile, dict filters)
287 Obex::Error Obex::pullAll(const char *type, unsigned long count) {
291 LoggerD("Invalid input argument(s)");
292 return OBEX_ERR_INVALID_ARGUMENTS;
296 LoggerE("No session to execute operation on");
297 initiateNextSyncRequest();
298 return OBEX_ERR_INVALID_SESSION;
304 GVariant *filters[8];
307 GVariant *name, *str, *var;
308 // "Format" -> "vcard30"
309 name = g_variant_new_string("Format");
310 str = g_variant_new_string("vcard30");
311 var = g_variant_new_variant(str);
312 filters[nfilters++] = g_variant_new_dict_entry(name, var);
314 // "Offset" -> Offset of the first item, default is 0
316 name = g_variant_new_string("MaxCount");
317 str = g_variant_new_uint16(count);
318 var = g_variant_new_variant(str);
319 filters[nfilters++] = g_variant_new_dict_entry(name, var);
322 GVariant *array = g_variant_new_array(G_VARIANT_TYPE("{sv}"), filters, nfilters);
324 // build the parameters variant
325 GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{sv})"));
326 g_variant_builder_add_value(builder, g_variant_new("s", "")); // target file name will be automatically calculated
327 g_variant_builder_add_value(builder, array);
328 GVariant *parameters = g_variant_builder_end(builder);
330 reply = g_dbus_connection_call_sync( g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL),
333 OBEX_PHONEBOOK_IFACE,
337 G_DBUS_CALL_FLAGS_NONE,
343 LoggerE("Failed to 'PullAll': " << err->message);
344 initiateNextSyncRequest();
346 return OBEX_ERR_DBUS_ERROR;
350 LoggerE("Reply from call 'PullAll' is NULL");
351 initiateNextSyncRequest();
352 return OBEX_ERR_DBUS_INVALID_REPLY;
355 const char *transfer = NULL;
357 g_variant_get(reply, "((oa{sv}))", &transfer, &iter);
358 LoggerD("transfer path = " << transfer);
359 mActiveTransfer = strdup(transfer);
360 g_timeout_add(CHECK_STALLED_TRANSFER_TIMEOUT*1000,
361 Obex::checkStalledTransfer,
362 new CtxCbData(this, NULL, strdup(transfer), NULL));
364 // let's abuse 'cb' field from CtxCbData to store selected remote device's MAC
365 CtxCbData *data = new CtxCbData(this, strdup(mSelectedRemoteDevice.c_str()), NULL, (void*)type);
366 // we can set signal listener for "Error" from here,
367 // since we don't need to know file path of stored transfer data
368 Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
369 OBEX_TRANSFER_IFACE, transfer, "Error",
370 Obex::handleSignal, data); //TODO: think of using 'error' callback here,
371 // or if error occurs, returns empty NULL string
375 while(g_variant_iter_next(iter, "{sv}", &key, &value)) {
376 if(!strcmp(key, "Size")) { // "Size"
377 //guint64 val = g_variant_get_uint64(value);
378 //LoggerD(key << " = " << val);
380 else { // "Name", "Filename"
381 //LoggerD(key << " = " << g_variant_get_string(value, NULL));
382 if(!strcmp(key, "Filename")) {
383 const char *fileName = g_variant_get_string(value, NULL);
385 LoggerD("Saving pulled data/VCards into: " << fileName);
386 // we call subscribe for "Complete" signal here, since we need to know path of stored file
387 // !!! what if signal comes before we subscribe for it? ... CAN IT? ... signals from DBUS
388 // should be executed in the thread the registration was made from, ie. this method has to
389 // to be completed first
390 data->data1 = strdup(fileName);
391 Utils::setSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
392 OBEX_TRANSFER_IFACE, transfer, "Complete",
393 Obex::handleSignal, data);
399 g_variant_unref(reply);
401 return OBEX_ERR_NONE;
404 gboolean Obex::checkStalledTransfer(gpointer user_data) {
405 CtxCbData *data = static_cast<CtxCbData*>(user_data);
407 return G_SOURCE_REMOVE; // single shot timeout
409 Obex *ctx = static_cast<Obex*>(data->ctx);
410 char *transfer = static_cast<char*>(data->data1);
411 if(!ctx || !transfer) {
412 LoggerE("Failed to cast to Obex");
413 return G_SOURCE_REMOVE; // single shot timeout
415 if(ctx->mActiveTransfer && !strcmp(ctx->mActiveTransfer, transfer)) {
416 LoggerD("The active transfer is Stalled");
417 ctx->clearSyncQueue();
418 ctx->transferStalled();
422 return G_SOURCE_REMOVE; // single shot timeout
425 Obex::Error Obex::syncContacts(unsigned long count) {
428 LoggerD("Session not created, you have to call createSession before calling any method");
429 return OBEX_ERR_INVALID_SESSION;
431 mSyncQueue.push_back(new SyncPBData("INT", "pb", count));
433 // if the size is one, that means that there has not been
434 // synchronization on-going and therefore we can initiate
435 // synchronization from here, otherwise it will be initiated
436 // once the current one will have finished
437 if(mSyncQueue.size() == 1) {
438 SyncPBData *sync = mSyncQueue.front();
439 LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
440 if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
441 if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
442 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
443 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
444 initiateNextSyncRequest();
449 return OBEX_ERR_NONE;
452 void Obex::getContactByPhoneNumber(const char *phoneNumber, std::string &contact) {
454 // return empty JSON contact, in case phoneNumber is invalid
459 for(auto it=mContacts.begin(); it!=mContacts.end(); ++it) {
460 GList *phoneNumbersList = (GList*)e_contact_get((*it).second, E_CONTACT_TEL);
461 if(phoneNumbersList) {
462 const char *phoneNumberToCheck = phoneNumbersList->data?(const char*)phoneNumbersList->data:NULL;
463 if(phoneNumberToCheck && !strcmp(phoneNumberToCheck, phoneNumber)) {
464 parseEContactToJsonTizenContact((*it).second, contact);
465 g_list_free(phoneNumbersList);
468 g_list_free(phoneNumbersList);
472 // if the contact is not found, return empty JSON contact
476 void Obex::getJsonContacts(std::string& contacts, unsigned long count) {
481 // if count == 0, ie. return all contacts
482 count = (count>0 && count<mContactsOrder.size())?count:mContactsOrder.size();
484 for(unsigned int i = 0; i<count; ++i) { // get 'count' latest contacts, ie. 'count' first from the list
485 EContact *item = mContacts[mContactsOrder.at(i)];
486 if(item) { // make sure, that the item exists
487 //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
488 if(contacts.compare("[")) // exclude ',' for the first entry
491 parseEContactToJsonTizenContact(item, contact);
499 void Obex::parseEContactToJsonTizenContact(EContact *econtact, std::string &contact) {
500 const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
502 if(!econtact || !uid) {
503 contact = "{}"; // empty contact
509 contact += "\"uid\":\"";
514 GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
515 const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:NULL; // phoneNumber is used as personId - first number from the list is used
516 if(personId && strcmp(personId, "")) {
517 contact += ",\"personId\":\"";
521 // DON'T free the list yet, it will be used later in the function
522 //if(phoneNumbersList)
523 // g_list_free(phoneNumbersList);
525 // addressBookId: not parsed
527 // lastUpdated: not parsed
529 // isFavorite: not parsed
532 contact += ",\"name\":{";
533 const char *firstName = (const char*)e_contact_get_const(econtact, E_CONTACT_GIVEN_NAME);
534 const char *lastName = (const char*)e_contact_get_const(econtact, E_CONTACT_FAMILY_NAME);
535 const char *displayName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
536 bool firstAttr = true; // used to indicate whether separating comma should be used
537 if(firstName && strcmp(firstName, "")) {
539 contact += "\"firstName\":\"";
540 contact += firstName;
543 if(lastName && strcmp(lastName, "")) {
545 contact += "\"lastName\":\"";
547 contact += ",\"lastName\":\"";
552 if(displayName && strcmp(displayName, "")) {
554 contact += "\"displayName\":\"";
556 contact += ",\"displayName\":\"";
558 contact += displayName;
564 contact += ",\"addresses\":[";
565 for(int id=E_CONTACT_ADDRESS_HOME; id<=E_CONTACT_ADDRESS_OTHER; id++) {
566 EContactAddress *address = (EContactAddress*)e_contact_get(econtact, (EContactField)id);
569 contact += "\"isDefault\":\"false\"";
570 if(address->country && strcmp(address->country,"")) {
571 contact += ",\"country\":\"";
572 contact += address->country;
575 if(address->region && strcmp(address->region,"")) {
576 contact += ",\"region\":\"";
577 contact += address->region;
580 if(address->locality && strcmp(address->locality,"")) {
581 contact += ",\"city\":\"";
582 contact += address->locality;
585 if(address->street && strcmp(address->street,"")) {
586 contact += ",\"streetAddress\":\"";
587 contact += address->street;
590 if(address->code && strcmp(address->code,"")) {
591 contact += ",\"postalCode\":\"";
592 contact += address->code;
595 contact += ",\"types\":[\"";
596 contact += id==E_CONTACT_ADDRESS_HOME ? "HOME" : (id==E_CONTACT_ADDRESS_WORK ? "WORK" : (id==E_CONTACT_ADDRESS_OTHER ? "OTHER" : ""));
600 e_contact_address_free(address);
606 EContactPhoto *photo = (EContactPhoto*)e_contact_get(econtact, E_CONTACT_PHOTO);
608 // we should have only URI type of contact photo, ... see processVCards() method
609 if(E_CONTACT_PHOTO_TYPE_URI == photo->type) {
610 const char *uri = e_contact_photo_get_uri(photo);
611 if(uri && strcmp(uri, "")) {
612 //LoggerD("photoURI = " << uri);
613 contact += ",\"photoURI\":\"";
619 e_contact_photo_free(photo);
622 contact += ",\"phoneNumbers\":[";
623 bool firstNumber = true;
624 while(phoneNumbersList && phoneNumbersList->data) {
625 const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:"";
626 if(phoneNumber && strcmp(phoneNumber, "")) {
628 contact += "{\"number\":\"";
630 contact += ",{\"number\":\"";
632 contact += phoneNumber;
635 phoneNumbersList = phoneNumbersList->next;
638 // now we can free the list
640 g_list_free(phoneNumbersList);
643 contact += ",\"emails\":[";
644 bool firstEmail = true;
645 for(int id=E_CONTACT_FIRST_EMAIL_ID; id<=E_CONTACT_LAST_EMAIL_ID; id++) {
646 const char *email = (const char*)e_contact_get_const(econtact, (EContactField)id);
647 if(email && strcmp(email, "")) {
649 contact += "{\"email\":\"";
651 contact += ",{\"email\":\"";
655 contact += ",\"isDefault\":\"false\""; // TODO: ?use 'firstEmail' value to set the first e-mail address as default?
656 contact += ",\"types\":[\"WORK\"]"; // just some default value
664 // birthday: not parsed
666 // anniversaries: not parsed
668 // organizations: not parsed
674 // ringtoneURI: not parsed
676 // groupIds: not parsed
681 Obex::Error Obex::syncCallHistory(unsigned long count) {
684 LoggerD("Session not created, you have to call createSession before calling any method");
685 return OBEX_ERR_INVALID_SESSION;
687 mSyncQueue.push_back(new SyncPBData("INT", "cch", count));
689 // if the size is one, that means that there has not been
690 // synchronization on-going and therefore we can initiate
691 // synchronization from here, otherwise it will be initiated
692 // once the current one will have finished
693 if(mSyncQueue.size() == 1) {
694 SyncPBData *sync = mSyncQueue.front();
695 LoggerD("synchronizing data: " << sync->location << "/" << sync->phonebook << " count=" << count);
696 if(select(sync->location, sync->phonebook)) { // do call 'pullAll' only if 'select' operation was successful
697 if(OBEX_ERR_NONE != pullAll(sync->phonebook, sync->count)) {
698 // 'PullAll' has not started at all, ie. there will be no 'Complete'/'Error' signals
699 // on 'Transport' - no signal at all, threfore go to next sync request from sync queue
700 initiateNextSyncRequest();
705 return OBEX_ERR_NONE;
708 void Obex::getJsonCallHistory(std::string& calls, unsigned long count) {
713 // if count == 0, ie. return all calls
714 count = (count>0 && count<mCallHistoryOrder.size())?count:mCallHistoryOrder.size();
716 for(unsigned int i = 0; i<count; ++i) { // get 'count' latest calls, ie. 'count' first from the list
717 EContact *item = mCallHistory[mCallHistoryOrder.at(i)];
718 if(item) { // make sure, that the item exists
719 //if(i!=0) // exclude ',' for the first entry - DON'T compare it against the index - What if first item is not found in the map?
720 if(calls.compare("[")) // exclude ',' for the first entry
723 parseEContactToJsonTizenCallHistoryEntry(item, call);
731 void Obex::parseEContactToJsonTizenCallHistoryEntry(EContact *econtact, std::string &call) {
732 const char *uid = (const char*)e_contact_get_const(econtact, E_CONTACT_UID);
733 if(!econtact || !uid) {
734 call = "{}"; // empty call history entry
740 call += "\"uid\":\"";
744 // type: not parsing - use some DEFAULT value, eg. "TEL"
745 call += "\"type\":\"TEL\",";
747 // features: not parsing - use some DEFAULT value, eg. "VOICECALL"
748 call += "\"features\":[\"VOICECALL\"],";
751 call += "\"remoteParties\":[";
753 call += "\"personId\":\"";
754 GList *phoneNumbersList = (GList*)e_contact_get(econtact, E_CONTACT_TEL);
755 const char *personId = (phoneNumbersList && phoneNumbersList->data)?(const char*)phoneNumbersList->data:""; // phoneNumber is used as personId - first number from the list is used
759 g_list_free(phoneNumbersList);
760 const char *fullName = (const char*)e_contact_get_const(econtact, E_CONTACT_FULL_NAME);
761 if(fullName && strcmp(fullName, "")) {
762 call += ",\"remoteParty\":\"";
770 const char *startTime = (const char*)e_contact_get_const(econtact, E_CONTACT_REV); // 'REV' holds call date/time
771 if(startTime && strcmp(startTime, "")) {
772 std::string startTimeStr = startTime;
773 startTimeStr.insert(13,":");
774 startTimeStr.insert(11,":");
775 startTimeStr.insert(6,"-");
776 startTimeStr.insert(4,"-");
777 call += "\"startTime\":\"";
778 call += startTimeStr;
782 // duration: not parsing - use 0 as default value
783 call += "\"duration\":\"0\",";
786 call += "\"direction\":\"";
787 const char *direction = (const char*)e_contact_get_const(econtact, E_CONTACT_NOTE); // 'NOTE' holds direction of the call
788 call += direction?direction:(char*)"UNDEFINED";
794 void Obex::handleSignal(GDBusConnection *connection,
796 const gchar *object_path,
797 const gchar *interface_name,
798 const gchar *signal_name,
799 GVariant *parameters,
802 LoggerD("signal received: '" << interface_name << "' -> '" << signal_name << "' -> '" << object_path << "'");
804 CtxCbData *data = static_cast<CtxCbData*>(user_data);
806 LoggerE("Failed to cast object: CtxCbData");
809 Obex *ctx = static_cast<Obex*>(data->ctx);
811 LoggerE("Failed to cast object: Obex");
815 if(!strcmp(interface_name, OBEX_TRANSFER_IFACE)) {
816 if(!strcmp(signal_name, "Complete") || !strcmp(signal_name, "Error")) {
817 Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
818 OBEX_TRANSFER_IFACE, object_path,
820 Utils::removeSignalListener(G_BUS_TYPE_SESSION, OBEX_CLIENT_SERVICE,
821 OBEX_TRANSFER_IFACE, object_path,
823 if(ctx->mActiveTransfer) {
824 free(ctx->mActiveTransfer);
825 ctx->mActiveTransfer = NULL;
828 if(!strcmp(signal_name, "Complete")) {
829 const char *path = static_cast<const char *>(data->data1);
830 const char *type = static_cast<const char *>(data->data2);
831 const char *origin = static_cast<const char *>(data->cb);
832 ctx->processVCards(path, type, origin);
834 else if(!strcmp(signal_name, "Error")) {
835 // dont' have to do anything
837 if(data->data1) free(data->data1); // path - to the file containing received VCards
838 if(data->cb) free(data->cb); // origin - MAC address of selected remote device
840 ctx->initiateNextSyncRequest();
845 void Obex::processVCards(const char *filePath, const char *type, const char *origin) {
848 if(!filePath || !type || !origin) {
849 LoggerE("Invalid argument(s)");
853 if(strcmp(origin, mSelectedRemoteDevice.c_str())) {
854 LoggerD("Received VCards don't belong to currently selected device - IGNORING");
858 std::map<std::string, EContact*> *items = NULL;
859 std::vector<std::string> *order = NULL;
860 if(!strcmp(type, "pb")) { // Contacts
862 order = &mContactsOrder;
864 else if(!strcmp(type, "cch")) { // CallHistory
865 items = &mCallHistory;
866 order = &mCallHistoryOrder;
868 // if the size of items map is 0, ie. that the received
869 // VCards are from first sync request and they should
870 // be added to the map (uid order vector) in the order they
871 // are processed (push_back), otherwise they should be
872 // inserted at the front (push_front)
873 bool firstData = items->size() == 0 ? true : false;
875 // process VCards one-by-one
876 std::ifstream file(filePath);
878 for(std::string line; getline(file, line);)
880 //std::replace( line.begin(), line.end(), '\r', '\n'); // removes carriage return
881 line.replace(line.find("\r"), 1, "\n");
883 if(line.find("BEGIN:VCARD") == 0) {
884 vcard = line; // start collecting new VCard
886 else if(line.find("END:VCARD") == 0) {
889 // start processing VCard
890 //printf("%s\n", vcard.c_str());
892 EContact *item = e_contact_new_from_vcard(vcard.c_str());
894 LoggerD("Failed to create EContact from vcard");
898 // won't use E_CONTACT_UID as a key to the map, since it is not returned by all phone devices
900 // failed to create UID from EContact
901 // won't add the entry to the list - UID used as a key to the map
904 const char *uid = (const char*)e_contact_get_const(item, E_CONTACT_UID);
906 // check if item has photo and it's INLINED type
907 // if so, change it to URI type, since the data are in binary form
908 // and as such can't be processed in JSON directly
909 // to avoid yet another conversion to eg. BASE64 format, save the
910 // contact photo in /tmp and use the URI to reference the photo instead
911 EContactPhoto *photo = (EContactPhoto*)e_contact_get(item, E_CONTACT_PHOTO);
913 if(E_CONTACT_PHOTO_TYPE_INLINED == photo->type) {
915 const guchar *data = e_contact_photo_get_inlined (photo, &length);
916 //uid is used as a file name
918 snprintf(fileName, sizeof(fileName), "/tmp/%s.jif", uid);
919 FILE *fd = fopen(fileName,"wb");
921 LoggerD("Unable to store contact photo: " << fileName);
924 LoggerD("Saving contact photo: " << fileName);
925 size_t written = fwrite(data, sizeof(guchar), length, fd);
927 if(written == length) {
928 // contact photo has been successfully saved
929 // change photo attribute from INLINED to URI
930 e_contact_photo_free(photo);
932 photo = e_contact_photo_new();
934 photo->type = E_CONTACT_PHOTO_TYPE_URI;
935 //e_contact_photo_set_mime_type(photo, "");
937 snprintf(uri, sizeof(uri), "file://%s", fileName);
938 e_contact_photo_set_uri(photo, uri);
939 e_contact_set(item, E_CONTACT_PHOTO, photo);
944 e_contact_photo_free(photo);
947 // check if an item with the given UID exists in the list
948 if((*items)[uid] == NULL) {
949 //LoggerD("NEW ITEM: " << uid);
950 (*items)[uid] = item;
952 order->push_back(uid);
954 order->insert(order->begin(), uid); // push at the front
955 if(!strcmp(type, "cch")) { // notify only for CallHistory
957 parseEContactToJsonTizenCallHistoryEntry(item, entry);
958 callHistoryEntryAdded(entry);
962 // the item already exists in the list, unref the item,
963 // since we loose any reference to it
964 g_object_unref(item);
968 // the current implementation of EContact doesn't support
969 // X-IRMC-CALL-DATETIME field, so as a workaround we use
970 // two separate fields instead: E_CONTACT_NOTE
972 if((line.find("NOTE") == 0) || (line.find("REV") == 0)) {
973 // exclude NOTE and REV as we are using it to store
974 // X-IRMC-CALL-DATETIME attribute
975 // exclude = do not copy it to vcard
977 else if(line.find("UID") == 0) {
978 // exclude UID as we are creating own UID
979 // exclude = do not copy it to vcard
981 else if(line.find("X-IRMC-CALL-DATETIME") == 0) {
982 size_t index1 = line.find( "TYPE=" ) + 5;
983 size_t index2 = line.find( ":", index1 ) + 1;
985 std::string note = line.substr (index1, index2-index1-1);
986 std::string rev = line.substr (index2, line.length()-index2);
988 vcard += "NOTE:" + note + "\n";
989 vcard += "REV:" + rev; // '\n' is taken from 'line'
997 // notify listener about Contacts/CallHistory being changed/synchronized
999 if(!strcmp(type, "pb")) // Contacts
1001 else if(!strcmp(type, "cch")) // CallHistory
1002 callHistoryChanged();
1006 bool Obex::makeUid(EContact *entry) {
1007 // use combination of phone number, given/family name and the modification date
1008 const char *_uid = (const char*)e_contact_get_const(entry, E_CONTACT_UID);
1010 // we shouldn't get here, since E_CONTACT_UID is filtered-out in processVCards() method
1011 // does "e_contact_set" frees the memory if the field already exists?
1014 GList *phoneNumbersList = (GList*)e_contact_get(entry, E_CONTACT_TEL);
1015 const char *phoneNumber = (phoneNumbersList && phoneNumbersList->data) ? (const char*)phoneNumbersList->data : NULL;
1016 const char *givenName = (const char*)e_contact_get_const(entry, E_CONTACT_GIVEN_NAME);
1017 const char *familyName = (const char*)e_contact_get_const(entry, E_CONTACT_FAMILY_NAME);
1018 const char *call_rev = (const char*)e_contact_get_const(entry, E_CONTACT_REV);
1020 if((!phoneNumber || !phoneNumber[0]) && (!givenName || !givenName[0]) && (!familyName || !familyName[0]) && (!call_rev || !call_rev[0])) {
1021 // uid is used as a key to the map
1022 LoggerD("Invalid EContact entry - not adding to the list");
1023 if(phoneNumbersList)
1024 g_list_free(phoneNumbersList);
1029 snprintf(uid, sizeof(uid), "%s:%s:%s:%s", phoneNumber?phoneNumber:"",
1030 givenName?givenName:"",
1031 familyName?familyName:"",
1032 call_rev?call_rev:"");
1035 // does "e_contact_set" make a copy of value, or
1036 // do we need make a copy of the value on the HEAP?
1037 e_contact_set(entry, E_CONTACT_UID, uid);
1039 if(phoneNumbersList)
1040 g_list_free(phoneNumbersList);