2 * Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
4 * Copyright (C) 2012 BMW Car IT GmbH. All rights reserved.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) version 3.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 #include "PbapSyncSource.h"
28 #include <boost/assign/list_of.hpp>
29 #include <boost/algorithm/string/case_conv.hpp>
30 #include <boost/tokenizer.hpp>
38 #include <syncevo/GLibSupport.h> // PBAP backend does not compile without GLib.
39 #include <syncevo/util.h>
40 #include <syncevo/BoostHelper.h>
42 #include "gdbus-cxx-bridge.h"
44 #include <boost/algorithm/string/predicate.hpp>
45 #include <boost/bind.hpp>
47 #include <synthesis/SDK_util.h>
49 #include <syncevo/SyncContext.h>
50 #include <syncevo/declarations.h>
53 #define OBC_SERVICE "org.openobex.client" // obexd < 0.47
54 #define OBC_SERVICE_NEW "org.bluez.obex.client" // obexd >= 0.47
55 #define OBC_SERVICE_NEW5 "org.bluez.obex" // obexd in Bluez 5.0
56 #define OBC_CLIENT_INTERFACE "org.openobex.Client"
57 #define OBC_CLIENT_INTERFACE_NEW "org.bluez.obex.Client"
58 #define OBC_CLIENT_INTERFACE_NEW5 "org.bluez.obex.Client1"
59 #define OBC_PBAP_INTERFACE "org.openobex.PhonebookAccess"
60 #define OBC_PBAP_INTERFACE_NEW "org.bluez.obex.PhonebookAccess"
61 #define OBC_PBAP_INTERFACE_NEW5 "org.bluez.obex.PhonebookAccess1"
62 #define OBC_TRANSFER_INTERFACE_NEW "org.bluez.obex.Transfer"
63 #define OBC_TRANSFER_INTERFACE_NEW5 "org.bluez.obex.Transfer1"
65 typedef std::map<int, pcrecpp::StringPiece> Content;
69 std::string m_buffer; // vCards kept in memory when using old obexd.
70 TmpFile m_tmpFile; // Stored in temporary file and mmapped with more recent obexd.
71 Content m_content; // Refers to chunks of m_buffer or m_tmpFile without copying them.
72 int m_numContacts; // Number of existing contacts, according to GetSize() or after downloading.
73 int m_currentContact; // Numbered starting with zero according to discovery in addVCards.
74 boost::shared_ptr<PbapSession> m_session; // Only set when there is a transfer ongoing.
75 int m_tmpFileOffset; // Number of bytes already parsed.
77 friend class PbapSession;
79 std::string getNextID();
80 bool getContact(int contactNumber, pcrecpp::StringPiece &vcard);
81 const char *addVCards(int startIndex, const pcrecpp::StringPiece &content);
90 class PbapSession : private boost::noncopyable {
92 static boost::shared_ptr<PbapSession> create(PbapSyncSource &parent);
94 void initSession(const std::string &address, const std::string &format);
96 typedef std::map<std::string, pcrecpp::StringPiece> Content;
97 typedef std::map<std::string, boost::variant<std::string> > Params;
99 boost::shared_ptr<PullAll> startPullAll(PullData pullata);
100 void checkForError(); // Throws exception if transfer failed.
101 Timespec transferComplete() const;
102 void resetTransfer();
106 PbapSession(PbapSyncSource &parent);
108 PbapSyncSource &m_parent;
109 boost::weak_ptr<PbapSession> m_self;
110 std::auto_ptr<GDBusCXX::DBusRemoteObject> m_client;
112 OBEXD_OLD, // obexd < 0.47
113 OBEXD_NEW, // obexd == 0.47, file-based transfer
114 // OBEXD_048 // obexd == 0.48, file-based transfer without SetFilter and with filter parameter to PullAll()
115 BLUEZ5 // obexd in Bluez >= 5.0
118 /** filter parameters for BLUEZ5 PullAll */
119 typedef std::list<std::string> Properties;
120 typedef boost::variant< std::string, Properties > Bluez5Values;
121 std::map<std::string, Bluez5Values> m_filter5;
122 Properties m_filterFields;
123 Properties supportedProperties() const;
126 * m_transferComplete will be set to the current monotonic time when observing a
127 * "Complete" signal on a transfer object path which has the
128 * current session as prefix. There may be more than one such transfer,
129 * so record all completions that we see and then pick the right one.
131 * It also gets set when an error occurred for such a transfer,
132 * in which case m_error will also be set.
134 * This only works as long as the session is only used for a
135 * single transfer. Otherwise a more complex tracking of
136 * completion, for example per transfer object path, is needed.
140 Timespec m_transferComplete;
141 std::string m_transferErrorCode;
142 std::string m_transferErrorMsg;
144 static Completion now() {
146 res.m_transferComplete = Timespec::monotonic();
150 typedef std::map<std::string, Completion> Transfers;
151 Transfers m_transfers;
152 std::string m_currentTransfer;
154 std::auto_ptr<GDBusCXX::SignalWatch3<GDBusCXX::Path_t, std::string, std::string> >
156 void errorCb(const GDBusCXX::Path_t &path, const std::string &error,
157 const std::string &msg);
160 typedef GDBusCXX::SignalWatch4<GDBusCXX::Path_t, std::string, Params, std::vector<std::string> > PropChangedSignal_t;
161 std::auto_ptr<PropChangedSignal_t> m_propChangedSignal;
162 void propChangedCb(const GDBusCXX::Path_t &path,
163 const std::string &interface,
164 const Params &changed,
165 const std::vector<std::string> &invalidated);
168 typedef GDBusCXX::SignalWatch1<GDBusCXX::Path_t> CompleteSignal_t;
169 std::auto_ptr<CompleteSignal_t> m_completeSignal;
170 void completeCb(const GDBusCXX::Path_t &path);
171 typedef GDBusCXX::SignalWatch3<GDBusCXX::Path_t, std::string, boost::variant<int64_t> > PropertyChangedSignal_t;
172 std::auto_ptr<PropertyChangedSignal_t> m_propertyChangedSignal;
173 void propertyChangedCb(const GDBusCXX::Path_t &path, const std::string &name, const boost::variant<int64_t> &value);
175 std::auto_ptr<GDBusCXX::DBusRemoteObject> m_session;
178 PbapSession::PbapSession(PbapSyncSource &parent) :
183 boost::shared_ptr<PbapSession> PbapSession::create(PbapSyncSource &parent)
185 boost::shared_ptr<PbapSession> session(new PbapSession(parent));
186 session->m_self = session;
190 void PbapSession::propChangedCb(const GDBusCXX::Path_t &path,
191 const std::string &interface,
192 const Params &changed,
193 const std::vector<std::string> &invalidated)
195 // Called for a path which matches the current session, so we know
196 // that the signal is for our transfer. Only need to check the status.
197 Params::const_iterator it = changed.find("Status");
198 if (it != changed.end()) {
199 std::string status = boost::get<std::string>(it->second);
200 SE_LOG_DEBUG(NULL, "OBEXD transfer %s: %s",
201 path.c_str(), status.c_str());
202 Completion completion = Completion::now();
203 SE_LOG_DEBUG(NULL, "obexd transfer %s: %s", path.c_str(), status.c_str());
204 if (status == "error") {
205 // We have to make up some error descriptions. The Bluez
206 // 5 API no longer seems to provide that.
207 completion.m_transferErrorCode = "transfer failed";
208 completion.m_transferErrorMsg = "reason unknown";
210 m_transfers[path] = completion;
214 void PbapSession::completeCb(const GDBusCXX::Path_t &path)
216 SE_LOG_DEBUG(NULL, "obexd transfer %s completed", path.c_str());
217 m_transfers[path] = Completion::now();
220 void PbapSession::errorCb(const GDBusCXX::Path_t &path,
221 const std::string &error,
222 const std::string &msg)
224 SE_LOG_DEBUG(NULL, "obexd transfer %s failed: %s %s",
225 path.c_str(), error.c_str(), msg.c_str());
226 Completion &completion = m_transfers[path];
227 completion.m_transferComplete = Timespec::monotonic();
228 completion.m_transferErrorCode = error;
229 completion.m_transferErrorMsg = msg;
232 void PbapSession::propertyChangedCb(const GDBusCXX::Path_t &path,
233 const std::string &name,
234 const boost::variant<int64_t> &value)
236 const int64_t *tmp = boost::get<int64_t>(&value);
238 SE_LOG_DEBUG(NULL, "obexd transfer %s property change: %s = %ld",
239 path.c_str(), name.c_str(), (long signed)*tmp);
241 SE_LOG_DEBUG(NULL, "obexd transfer %s property change: %s",
242 path.c_str(), name.c_str());
246 PbapSession::Properties PbapSession::supportedProperties() const
249 static const std::set<std::string> supported =
250 boost::assign::list_of("VERSION")
277 BOOST_FOREACH (const std::string &prop, m_filterFields) {
278 // Be conservative and only ask for properties that we
279 // really know how to use. obexd also lists the bit field
280 // strings ("BIT01") but phones have been seen to reject
281 // queries when those were enabled.
282 if (supported.find(prop) != supported.end()) {
283 props.push_back(prop);
289 void PbapSession::initSession(const std::string &address, const std::string &format)
291 if (m_session.get()) {
295 // format string uses:
296 // [(2.1|3.0):][^]propname,propname,...
298 // 3.0:^PHOTO = download in vCard 3.0 format, excluding PHOTO
299 // 2.1:PHOTO = download in vCard 2.1 format, only the PHOTO
303 std::string properties;
304 const pcrecpp::RE re("(?:(2\\.1|3\\.0):?)?(\\^?)([-a-zA-Z,]*)");
305 if (!re.FullMatch(format, &version, &tmp, &properties)) {
306 m_parent.throwError(StringPrintf("invalid specification of PBAP vCard format (databaseFormat): %s",
309 char negated = tmp.c_str()[0];
310 if (version.empty()) {
311 // same default as in obexd
314 if (version != "2.1" && version != "3.0") {
315 m_parent.throwError(StringPrintf("invalid vCard version prefix in PBAP vCard format specification (databaseFormat): %s",
318 std::set<std::string> keywords;
319 boost::split(keywords, properties, boost::is_from_range(',', ','));
320 typedef std::map<std::string, boost::variant<std::string> > Params;
323 params["Target"] = std::string("PBAP");
326 GDBusCXX::DBusConnectionPtr conn = GDBusCXX::dbus_get_bus_connection("SESSION", NULL, true, NULL);
328 // We must attempt to use the new interface(s), otherwise we won't know whether
329 // the daemon exists or can be started.
331 m_client.reset(new GDBusCXX::DBusRemoteObject(conn,
333 OBC_CLIENT_INTERFACE_NEW5,
334 OBC_SERVICE_NEW5, true));
336 SE_LOG_DEBUG(NULL, "trying to use bluez 5 obexd service %s", OBC_SERVICE_NEW5);
338 GDBusCXX::DBusClientCall1<GDBusCXX::DBusObject_t>(*m_client, "CreateSession")(address, params);
339 } catch (const std::exception &error) {
340 if (!strstr(error.what(), "org.freedesktop.DBus.Error.ServiceUnknown") &&
341 !strstr(error.what(), "org.freedesktop.DBus.Error.UnknownObject")) {
344 // Fall back to old interface.
345 SE_LOG_DEBUG(NULL, "bluez obex service not available (%s), falling back to previous obexd one %s",
348 m_obexAPI = OBEXD_NEW;
351 if (session.empty()) {
352 m_client.reset(new GDBusCXX::DBusRemoteObject(conn, "/", OBC_CLIENT_INTERFACE_NEW,
353 OBC_SERVICE_NEW, true));
355 SE_LOG_DEBUG(NULL, "trying to use new obexd service %s", OBC_SERVICE_NEW);
357 GDBusCXX::DBusClientCall1<GDBusCXX::DBusObject_t>(*m_client, "CreateSession")(address, params);
358 } catch (const std::exception &error) {
359 if (!strstr(error.what(), "org.freedesktop.DBus.Error.ServiceUnknown")) {
362 // Fall back to old interface.
363 SE_LOG_DEBUG(NULL, "new obexd service(s) not available (%s), falling back to old one %s",
366 m_obexAPI = OBEXD_OLD;
370 if (session.empty()) {
371 m_client.reset(new GDBusCXX::DBusRemoteObject(conn, "/", OBC_CLIENT_INTERFACE,
373 params["Destination"] = std::string(address);
374 session = GDBusCXX::DBusClientCall1<GDBusCXX::DBusObject_t>(*m_client, "CreateSession")(params);
377 if (session.empty()) {
378 m_parent.throwError("PBAP: failed to create session");
381 if (m_obexAPI != OBEXD_OLD) {
382 m_session.reset(new GDBusCXX::DBusRemoteObject(m_client->getConnection(),
384 m_obexAPI == BLUEZ5 ? OBC_PBAP_INTERFACE_NEW5 : OBC_PBAP_INTERFACE_NEW,
385 m_obexAPI == BLUEZ5 ? OBC_SERVICE_NEW5 : OBC_SERVICE_NEW,
388 // Filter Transfer signals via path prefix. Discussions on Bluez
389 // list showed that this is meant to be possible, even though the
390 // client-api.txt documentation itself didn't (and still doesn't)
392 // "[PATCH obexd v0] client-doc: Guarantee prefix in transfer paths"
393 // http://www.spinics.net/lists/linux-bluetooth/msg28409.html
395 // Be extra careful with asynchronous callbacks: bind to weak
396 // pointer and ignore callback when the instance is already gone.
397 // Should not happen with signals (destructing the class unregisters
398 // the watch), but very well may happen in asynchronous method
399 // calls. Therefore maintain m_self and show how to use it here.
400 if (m_obexAPI == BLUEZ5) {
402 m_propChangedSignal.reset(new PropChangedSignal_t
403 (GDBusCXX::SignalFilter(m_client->getConnection(),
405 "org.freedesktop.DBus.Properties",
407 GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX)));
408 m_propChangedSignal->activate(boost::bind(&PbapSession::propChangedCb, m_self, _1, _2, _3, _4));
411 m_completeSignal.reset(new CompleteSignal_t
412 (GDBusCXX::SignalFilter(m_client->getConnection(),
414 OBC_TRANSFER_INTERFACE_NEW,
416 GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX)));
417 m_completeSignal->activate(boost::bind(&PbapSession::completeCb, m_self, _1));
420 m_errorSignal.reset(new GDBusCXX::SignalWatch3<GDBusCXX::Path_t, std::string, std::string>
421 (GDBusCXX::SignalFilter(m_client->getConnection(),
423 OBC_TRANSFER_INTERFACE_NEW,
425 GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX)));
426 m_errorSignal->activate(boost::bind(&PbapSession::errorCb, m_self, _1, _2, _3));
428 // and property changes
429 m_propertyChangedSignal.reset(new PropertyChangedSignal_t(GDBusCXX::SignalFilter(m_client->getConnection(),
431 OBC_TRANSFER_INTERFACE_NEW,
433 GDBusCXX::SignalFilter::SIGNAL_FILTER_PATH_PREFIX)));
434 m_propertyChangedSignal->activate(boost::bind(&PbapSession::propertyChangedCb, m_self, _1, _2, _3));
438 m_session.reset(new GDBusCXX::DBusRemoteObject(m_client->getConnection(),
445 SE_LOG_DEBUG(NULL, "PBAP session created: %s", m_session->getPath());
447 // get filter list so that we can continue validating our format specifier
448 m_filterFields = GDBusCXX::DBusClientCall1< Properties >(*m_session, "ListFilterFields")();
449 SE_LOG_DEBUG(NULL, "supported PBAP filter fields:\n %s",
450 boost::join(m_filterFields, "\n ").c_str());
454 // negated, start with everything set
455 filter = supportedProperties();
458 // validate parameters and update filter
459 BOOST_FOREACH (const std::string &prop, keywords) {
464 Properties::const_iterator entry =
465 std::find_if(m_filterFields.begin(),
466 m_filterFields.end(),
467 boost::bind(&boost::iequals<std::string,std::string>, _1, prop, std::locale()));
469 if (entry == m_filterFields.end()) {
470 m_parent.throwError(StringPrintf("invalid property name in PBAP vCard format specification (databaseFormat): %s",
475 filter.remove(*entry);
477 filter.push_back(*entry);
481 GDBusCXX::DBusClientCall0(*m_session, "Select")(std::string("int"), std::string("PB"));
482 m_filter5["Format"] = version == "2.1" ? "vcard21" : "vcard30";
483 m_filter5["Fields"] = filter;
485 SE_LOG_DEBUG(NULL, "PBAP session initialized");
488 boost::shared_ptr<PullAll> PbapSession::startPullAll(PullData pullData)
492 // Update prepared filter to match pullData.
493 std::map<std::string, Bluez5Values> currentFilter = m_filter5;
494 std::string &format = boost::get<std::string>(currentFilter["Format"]);
495 std::list<std::string> &filter = boost::get< std::list<std::string> >(currentFilter["Fields"]);
497 case PULL_AS_CONFIGURED:
498 SE_LOG_DEBUG(NULL, "pull all with configured filter: '%s'",
499 boost::join(filter, " ").c_str());
501 case PULL_WITHOUT_PHOTOS:
502 // Remove PHOTO from list or create list with the other properties.
503 if (filter.empty()) {
504 filter = supportedProperties();
506 for (Properties::iterator it = filter.begin();
509 if (*it == "PHOTO") {
514 SE_LOG_DEBUG(NULL, "pull all without photos: '%s'",
515 boost::join(filter, " ").c_str());
519 if (m_obexAPI == OBEXD_OLD ||
520 m_obexAPI == OBEXD_NEW) {
521 GDBusCXX::DBusClientCall0(*m_session, "SetFilter")(filter);
522 GDBusCXX::DBusClientCall0(*m_session, "SetFormat")(format);
525 boost::shared_ptr<PullAll> state(new PullAll);
526 state->m_currentContact = 0;
527 if (m_obexAPI != OBEXD_OLD) {
528 // Beware, this will lead to a "Complete" signal in obexd
529 // 0.47. We need to be careful with looking at the right
530 // transfer to determine whether PullAll completed.
531 state->m_numContacts = GDBusCXX::DBusClientCall1<uint16>(*m_session, "GetSize")();
532 SE_LOG_DEBUG(NULL, "Expecting %d contacts.", state->m_numContacts);
534 state->m_tmpFile.create();
535 SE_LOG_DEBUG(NULL, "Created temporary file for PullAll %s", state->m_tmpFile.filename().c_str());
536 GDBusCXX::DBusClientCall1<std::pair<GDBusCXX::DBusObject_t, Params> > pullall(*m_session, "PullAll");
537 std::pair<GDBusCXX::DBusObject_t, Params> tuple =
538 m_obexAPI == OBEXD_NEW ?
539 GDBusCXX::DBusClientCall1<std::pair<GDBusCXX::DBusObject_t, Params> >(*m_session, "PullAll")(state->m_tmpFile.filename()) :
540 GDBusCXX::DBusClientCall2<GDBusCXX::DBusObject_t, Params>(*m_session, "PullAll")(state->m_tmpFile.filename(), currentFilter);
541 const GDBusCXX::DBusObject_t &transfer = tuple.first;
542 const Params &properties = tuple.second;
543 m_currentTransfer = transfer;
544 SE_LOG_DEBUG(NULL, "pullall transfer path %s, %ld properties", transfer.c_str(), (long)properties.size());
545 // Work will be finished incrementally in PullAll::getContact().
547 // In the meantime we return IDs by simply enumerating the expected ones.
548 // If we don't get as many contacts as expected, we return 404 in getContact()
549 // and the Synthesis engine will ignore the ID (src/sysync/binfileimplds.cpp:
550 // "Record does not exist any more in database%s -> ignore").
551 state->m_tmpFileOffset = 0;
552 state->m_session = m_self.lock();
554 GDBusCXX::DBusClientCall1<std::string> pullall(*m_session, "PullAll");
555 state->m_buffer = pullall();
556 state->addVCards(0, state->m_buffer);
557 state->m_numContacts = state->m_content.size();
562 const char *PullAll::addVCards(int startIndex, const pcrecpp::StringPiece &vcards)
564 pcrecpp::StringPiece vcarddata;
565 pcrecpp::StringPiece tmp = vcards;
566 int count = startIndex;
567 pcrecpp::RE re("[\\r\\n]*(^BEGIN:VCARD.*?^END:VCARD)",
568 pcrecpp::RE_Options().set_dotall(true).set_multiline(true));
569 while (re.Consume(&tmp, &vcarddata)) {
570 m_content[count] = vcarddata;
574 SE_LOG_DEBUG(NULL, "PBAP content parsed: %d entries starting at %d", count - startIndex, startIndex);
578 void PbapSession::checkForError()
580 Transfers::const_iterator it = m_transfers.find(m_currentTransfer);
581 if (it != m_transfers.end()) {
582 if (!it->second.m_transferErrorCode.empty()) {
583 m_parent.throwError(StringPrintf("%s: %s",
584 it->second.m_transferErrorCode.c_str(),
585 it->second.m_transferErrorMsg.c_str()));
590 Timespec PbapSession::transferComplete() const
593 Transfers::const_iterator it = m_transfers.find(m_currentTransfer);
594 if (it != m_transfers.end()) {
595 res = it->second.m_transferComplete;
600 void PbapSession::resetTransfer()
605 std::string PullAll::getNextID()
608 if (m_currentContact < m_numContacts) {
609 id = StringPrintf("%d", m_currentContact);
615 bool PullAll::getContact(int contactNumber, pcrecpp::StringPiece &vcard)
617 SE_LOG_DEBUG(NULL, "get PBAP contact #%d", contactNumber);
618 if (contactNumber < 0 ||
619 contactNumber >= m_numContacts) {
620 SE_LOG_DEBUG(NULL, "invalid contact number");
624 Content::iterator it;
625 while ((it = m_content.find(contactNumber)) == m_content.end() &&
627 (!m_session->transferComplete() ||
628 m_tmpFile.moreData())) {
629 // Wait? We rely on regular propgress signals to wake us up.
630 // obex 0.47 sends them every 64KB, at least in combination
631 // with a Samsung Galaxy SIII. This may depend on both obexd
632 // and the phone, so better check ourselves and perhaps do it
633 // less often - unmap/map can be expensive and invalidates
634 // some of the unread data (at least how it is implemented
636 while (!m_session->transferComplete() && m_tmpFile.moreData() < 128 * 1024) {
637 g_main_context_iteration(NULL, true);
639 m_session->checkForError();
640 if (m_tmpFile.moreData()) {
641 // Remap. This shifts all addresses already stored in
642 // m_content, so beware and update those.
643 pcrecpp::StringPiece oldMem = m_tmpFile.stringPiece();
646 pcrecpp::StringPiece newMem = m_tmpFile.stringPiece();
647 ssize_t delta = newMem.data() - oldMem.data();
648 BOOST_FOREACH (Content::value_type &entry, m_content) {
649 pcrecpp::StringPiece &vcard = entry.second;
650 vcard.set(vcard.data() + delta, vcard.size());
653 // File exists and obexd has written into it, so now we
654 // can unlink it to avoid leaking it if we crash.
657 // Continue parsing where we stopped before.
658 pcrecpp::StringPiece next(newMem.data() + m_tmpFileOffset,
659 newMem.size() - m_tmpFileOffset);
660 const char *end = addVCards(m_content.size(), next);
661 int newTmpFileOffset = end - newMem.data();
662 SE_LOG_DEBUG(NULL, "PBAP content parsed: %d out of %d (total), %d out of %d (last update)",
665 (int)(end - next.data()),
667 m_tmpFileOffset = newTmpFileOffset;
671 if (it == m_content.end()) {
672 SE_LOG_DEBUG(NULL, "did not get the expected contact #%d, perhaps some contacts were deleted?", contactNumber);
679 void PbapSession::shutdown(void)
681 GDBusCXX::DBusClientCall0 removeSession(*m_client, "RemoveSession");
683 // always clear pointer, even if method call fails
684 GDBusCXX::DBusObject_t path(m_session->getPath());
686 SE_LOG_DEBUG(NULL, "removed session: %s", path.c_str());
690 SE_LOG_DEBUG(NULL, "PBAP session closed");
693 PbapSyncSource::PbapSyncSource(const SyncSourceParams ¶ms) :
696 SyncSourceSession::init(m_operations);
697 m_operations.m_readNextItem = boost::bind(&PbapSyncSource::readNextItem, this, _1, _2, _3);
698 m_operations.m_readItemAsKey = boost::bind(&PbapSyncSource::readItemAsKey,
700 m_session = PbapSession::create(*this);
701 const char *PBAPSyncMode = getenv("SYNCEVOLUTION_PBAP_SYNC");
702 m_PBAPSyncMode = !PBAPSyncMode ? PBAP_SYNC_NORMAL :
703 boost::iequals(PBAPSyncMode, "incremental") ? PBAP_SYNC_INCREMENTAL :
704 boost::iequals(PBAPSyncMode, "text") ? PBAP_SYNC_TEXT :
705 boost::iequals(PBAPSyncMode, "all") ? PBAP_SYNC_NORMAL :
706 (throwError(StringPrintf("invalid value for SYNCEVOLUTION_PBAP_SYNC: %s", PBAPSyncMode)), PBAP_SYNC_NORMAL);
707 m_isFirstCycle = true;
708 m_hadContacts = false;
711 PbapSyncSource::~PbapSyncSource()
715 void PbapSyncSource::open()
717 string database = getDatabaseID();
718 const string prefix("obex-bt://");
720 if (!boost::starts_with(database, prefix)) {
721 throwError("database should specifiy device address (obex-bt://<bt-addr>)");
724 std::string address = database.substr(prefix.size());
726 m_session->initSession(address, getDatabaseFormat());
729 void PbapSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
731 if (!lastToken.empty()) {
732 throwError(STATUS_SLOW_SYNC_508, std::string("PBAP cannot do change detection"));
736 std::string PbapSyncSource::endSync(bool success)
739 // Non-empty so that beginSync() can detect non-slow syncs and ask
744 bool PbapSyncSource::isEmpty()
746 return false; // We don't know for sure. Doesn't matter, so pretend to not be empty.
749 void PbapSyncSource::close()
751 m_session->shutdown();
754 PbapSyncSource::Databases PbapSyncSource::getDatabases()
758 result.push_back(Database("select database via bluetooth address",
759 "[obex-bt://]<bt-addr>"));
763 void PbapSyncSource::enableServerMode()
765 SE_THROW("PbapSyncSource does not implement server mode.");
768 bool PbapSyncSource::serverModeEnabled() const
773 std::string PbapSyncSource::getPeerMimeType() const
778 void PbapSyncSource::getSynthesisInfo(SynthesisInfo &info,
779 XMLConfigFragments &fragments)
781 // We send vCards in either 2.1 or 3.0. The Synthesis engine
782 // handles both when parsing, so we don't have to be exact here.
783 info.m_native = "vCard21";
784 info.m_fieldlist = "contacts";
785 info.m_profile = "\"vCard\", 1";
787 // vCard 3.0 is the more sane format for exchanging with the
788 // Synthesis peer in a local sync, so use that by default.
789 std::string type = "text/vcard";
790 SourceType sourceType = getSourceType();
791 if (!sourceType.m_format.empty()) {
792 type = sourceType.m_format;
794 info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
797 * Access to data must be done early so that a slow sync can be
800 info.m_earlyStartDataRead = true;
803 // TODO: return IDs based on GetSize(), read only when engine needs data.
805 sysync::TSyError PbapSyncSource::readNextItem(sysync::ItemID aID,
806 sysync::sInt32 *aStatus,
810 m_pullAll = m_session->startPullAll((m_PBAPSyncMode == PBAP_SYNC_TEXT ||
811 (m_PBAPSyncMode == PBAP_SYNC_INCREMENTAL && m_isFirstCycle)) ? PULL_WITHOUT_PHOTOS :
815 throwError("logic error: readNextItem without aFirst=true before");
817 std::string id = m_pullAll->getNextID();
819 *aStatus = sysync::ReadNextItem_EOF;
820 if (m_PBAPSyncMode == PBAP_SYNC_INCREMENTAL &&
823 requestAnotherSync();
824 m_isFirstCycle = false;
827 *aStatus = sysync::ReadNextItem_Unchanged;
828 aID->item = StrAlloc(id.c_str());
830 m_hadContacts = true;
832 return sysync::LOCERR_OK;
835 sysync::TSyError PbapSyncSource::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
838 throwError("logic error: readItemAsKey() without preceeding readNextItem()");
840 pcrecpp::StringPiece vcard;
841 if (m_pullAll->getContact(atoi(aID->item), vcard)) {
842 return getSynthesisAPI()->setValue(aItemKey, "data", vcard.data(), vcard.size());
844 return sysync::DB_NotFound;
848 SyncSourceRaw::InsertItemResult PbapSyncSource::insertItemRaw(const std::string &luid, const std::string &item)
850 throwError("writing via PBAP is not supported");
851 return InsertItemResult();
854 void PbapSyncSource::readItemRaw(const std::string &luid, std::string &item)
857 throwError("logic error: readItemRaw() without preceeding readNextItem()");
859 pcrecpp::StringPiece vcard;
860 if (m_pullAll->getContact(atoi(luid.c_str()), vcard)) {
861 item.assign(vcard.data(), vcard.size());
863 throwError(STATUS_NOT_FOUND, string("retrieving item: ") + luid);
869 #endif /* ENABLE_PBAP */
871 #ifdef ENABLE_MODULES
872 # include "PbapSyncSourceRegister.cpp"