2 * Copyright (C) 2012 Intel Corporation
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #include "individual-traits.h"
22 #include "persona-details.h"
23 #include "filtered-view.h"
24 #include "full-view.h"
25 #include "merge-view.h"
26 #include "edsf-view.h"
27 #include "../resource.h"
28 #include "../client.h"
29 #include "../session.h"
31 #include <syncevo/IniConfigNode.h>
32 #include <syncevo/BoostHelper.h>
34 #include <boost/lexical_cast.hpp>
35 #include <boost/scoped_ptr.hpp>
36 #include <boost/tokenizer.hpp>
43 static const char * const MANAGER_SERVICE = "org._01.pim.contacts";
44 static const char * const MANAGER_PATH = "/org/01/pim/contacts";
45 static const char * const MANAGER_IFACE = "org._01.pim.contacts.Manager";
46 static const char * const MANAGER_ERROR_ABORTED = "org._01.pim.contacts.Manager.Aborted";
47 static const char * const MANAGER_ERROR_BAD_STATUS = "org._01.pim.contacts.Manager.BadStatus";
48 static const char * const MANAGER_ERROR_ALREADY_EXISTS = "org._01.pim.contacts.Manager.AlreadyExists";
49 static const char * const MANAGER_ERROR_NOT_FOUND = "org._01.pim.contacts.Manager.NotFound";
50 static const char * const AGENT_IFACE = "org._01.pim.contacts.ViewAgent";
51 static const char * const CONTROL_IFACE = "org._01.pim.contacts.ViewControl";
53 static const char * const MANAGER_CONFIG_SORT_PROPERTY = "sort";
54 static const char * const MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY = "active";
57 * Prefix for peer databases ("peer-<uid>")
59 static const char * const DB_PEER_PREFIX = "peer-";
62 * Name prefix for SyncEvolution config contexts used by PIM manager.
63 * Used in combination with the uid string provided by the PIM manager
66 * eds@pim-manager-<uid> source 'eds' syncs with target-config@pim-manager-<uid>
67 * source 'remote' for PBAP.
69 * eds@pim-manager-<uid> source 'local' syncs with a SyncML peer directly.
71 static const char * const MANAGER_PREFIX = "pim-manager-";
72 static const char * const MANAGER_LOCAL_CONFIG = "eds";
73 static const char * const MANAGER_LOCAL_SOURCE = "local";
74 static const char * const MANAGER_REMOTE_CONFIG = "target-config";
75 static const char * const MANAGER_REMOTE_SOURCE = "remote";
77 Manager::Manager(const boost::shared_ptr<Server> &server) :
78 DBusObjectHelper(server->getConnection(),
81 m_mainThread(g_thread_self()),
83 m_locale(LocaleFactory::createFactory()),
84 emitSyncProgress(*this, "SyncProgress")
90 // Clear the pending queue before self-desctructing, because the
91 // entries hold pointers to this instance.
93 if (m_preventingAutoTerm) {
94 m_server->autoTermUnref();
98 #ifdef PIM_MANAGER_TEST_THREADING
99 static gpointer StartManager(gpointer data)
101 Manager *manager = static_cast<Manager *>(data);
109 // Restore sort order and active databases.
110 m_configNode.reset(new IniFileConfigNode(SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution"),
113 InitStateString order = m_configNode->readProperty(MANAGER_CONFIG_SORT_PROPERTY);
114 m_sortOrder = order.wasSet() ?
117 InitStateString active = m_configNode->readProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY);
118 m_enabledEBooks.clear();
119 typedef boost::split_iterator<string::iterator> string_split_iterator;
120 BOOST_FOREACH(const std::string &entry,
121 boost::tokenizer< boost::char_separator<char> >(active, boost::char_separator<char>(", \t"))) {
122 if (!entry.empty()) {
123 m_enabledEBooks.insert(entry);
127 initSorting(m_sortOrder);
130 add(this, &Manager::start, "Start");
131 add(this, &Manager::stop, "Stop");
132 add(this, &Manager::isRunning, "IsRunning");
133 add(this, &Manager::setSortOrder, "SetSortOrder");
134 add(this, &Manager::getSortOrder, "GetSortOrder");
135 add(this, &Manager::search, "Search");
136 add(this, &Manager::getActiveAddressBooks, "GetActiveAddressBooks");
137 add(this, &Manager::setActiveAddressBooks, "SetActiveAddressBooks");
138 add(this, &Manager::modifyPeer, "SetPeer"); // The original method name, keep it for backwards compatibility.
139 add(this, &Manager::createPeer, "CreatePeer"); // Strict version: uid must be new.
140 add(this, &Manager::removePeer, "RemovePeer");
141 add(this, &Manager::syncPeer, "SyncPeer");
142 add(this, &Manager::stopSync, "StopSync");
143 add(this, &Manager::getAllPeers, "GetAllPeers");
144 add(this, &Manager::addContact, "AddContact");
145 add(this, &Manager::modifyContact, "ModifyContact");
146 add(this, &Manager::removeContact, "RemoveContact");
147 add(emitSyncProgress);
149 // Ready, make it visible via D-Bus.
152 // Claim MANAGER_SERVICE name on connection.
153 // We don't care about the result.
154 GDBusCXX::DBusConnectionPtr(getConnection()).ownNameAsync(MANAGER_SERVICE,
155 boost::function<void (bool)>());
157 #ifdef PIM_MANAGER_TEST_THREADING
158 GThread *thread = g_thread_new("start",
161 g_thread_unref(thread);
170 boost::function<void ()> m_operation;
171 boost::function<void ()> m_rethrow;
175 g_mutex_lock(&m_mutex);
177 // Exceptions must be reported back to the original thread.
178 // This is done by serializing them as string, then using the
179 // existing Exception::tryRethrow() to turn that string back
180 // into an instance of the right class.
184 std::string explanation;
185 Exception::handle(explanation);
186 m_rethrow = boost::bind(Exception::tryRethrow, explanation, true);
191 g_cond_signal(&m_cond);
192 g_mutex_unlock(&m_mutex);
196 template <class R> void AssignResult(const boost::function<R ()> &operation,
202 template <class R> R Manager::runInMainRes(const boost::function<R ()> &operation)
207 g_mutex_init(&task.m_mutex);
208 g_cond_init(&task.m_cond);
210 task.m_operation = boost::bind(&AssignResult<R>, boost::cref(operation), boost::ref(res));
214 timeout.runOnce(-1, boost::bind(&TaskForMain::runTaskOnIdle, &task));
215 g_main_context_wakeup(NULL);
216 g_mutex_lock(&task.m_mutex);
217 while (!task.m_done) {
218 g_cond_wait(&task.m_cond, &task.m_mutex);
220 g_mutex_unlock(&task.m_mutex);
222 // Rethrow exceptions (optional) and return result.
223 g_cond_clear(&task.m_cond);
224 g_mutex_clear(&task.m_mutex);
225 if (task.m_rethrow) {
231 static int Return1(const boost::function<void ()> &operation)
237 void Manager::runInMainVoid(const boost::function<void ()> &operation)
239 runInMainRes<int>(boost::bind(Return1, boost::cref(operation)));
242 void Manager::initFolks()
244 m_folks = IndividualAggregator::create(m_locale);
247 void Manager::initSorting(const std::string &order)
249 // Mirror sorting order in m_folks main view.
250 // Empty string passes NULL pointer to setCompare(),
251 // which chooses the builtin sorting in folks.cpp,
252 // independent of the locale plugin.
253 boost::shared_ptr<IndividualCompare> compare =
255 IndividualCompare::defaultCompare() :
256 m_locale->createCompare(order);
257 m_folks->setCompare(compare);
260 boost::shared_ptr<Manager> Manager::create(const boost::shared_ptr<Server> &server)
262 boost::shared_ptr<Manager> manager(new Manager(server));
263 manager->m_self = manager;
268 boost::shared_ptr<GDBusCXX::DBusObjectHelper> CreateContactManager(const boost::shared_ptr<Server> &server, bool start)
270 boost::shared_ptr<Manager> manager = Manager::create(server);
277 void Manager::start()
280 runInMainV(&Manager::start);
284 if (!m_preventingAutoTerm) {
285 // Prevent automatic shut down during idle times, because we need
286 // to keep our unified address book available.
287 m_server->autoTermRef();
288 m_preventingAutoTerm = true;
296 runInMainV(&Manager::stop);
300 // If there are no active searches, then recreate aggregator.
301 // Instead of tracking open views, use the knowledge that an
302 // idle server has only two references to the main view:
303 // one inside m_folks, one given back to us here.
304 if (m_folks->getMainView().use_count() <= 2) {
305 SE_LOG_DEBUG(NULL, "restarting due to Manager.Stop()");
308 initSorting(m_sortOrder);
310 if (m_preventingAutoTerm) {
311 // Allow auto shutdown again.
312 m_server->autoTermUnref();
313 m_preventingAutoTerm = false;
318 bool Manager::isRunning()
321 return runInMainR(&Manager::isRunning);
324 return m_folks->isRunning();
327 void Manager::setSortOrder(const std::string &order)
330 runInMainV(&Manager::setSortOrder, order);
334 if (order == getSortOrder()) {
339 // String is checked as part of initSorting,
340 // only store if parsing succeeds.
342 m_configNode->writeProperty(MANAGER_CONFIG_SORT_PROPERTY, InitStateString(order, true));
343 m_configNode->flush();
347 std::string Manager::getSortOrder()
350 return runInMainR(&Manager::getSortOrder);
357 * Connects a normal IndividualView to a D-Bus client.
358 * Provides the org.01.pim.contacts.ViewControl API.
360 class ViewResource : public Resource, public GDBusCXX::DBusObjectHelper
362 static unsigned int m_counter;
364 boost::weak_ptr<ViewResource> m_self;
365 GDBusCXX::DBusRemoteObject m_viewAgent;
366 boost::shared_ptr<IndividualView> m_view;
367 boost::shared_ptr<LocaleFactory> m_locale;
368 boost::weak_ptr<Client> m_owner;
370 Change() : m_start(0), m_call(NULL) {}
373 std::deque<std::string> m_ids;
374 const GDBusCXX::DBusClientCall0 *m_call;
375 } m_pendingChange, m_lastChange;
376 GDBusCXX::DBusClientCall0 m_quiescent;
377 GDBusCXX::DBusClientCall0 m_contactsModified,
381 ViewResource(const boost::shared_ptr<IndividualView> view,
382 const boost::shared_ptr<LocaleFactory> &locale,
383 const boost::shared_ptr<Client> &owner,
384 GDBusCXX::connection_type *connection,
385 const GDBusCXX::Caller_t &ID,
386 const GDBusCXX::DBusObject_t &agentPath) :
387 GDBusCXX::DBusObjectHelper(connection,
388 StringPrintf("%s/view%d", MANAGER_PATH, m_counter++),
390 m_viewAgent(connection,
398 // use ViewAgent interface
399 m_quiescent(m_viewAgent, "Quiescent"),
400 m_contactsModified(m_viewAgent, "ContactsModified"),
401 m_contactsAdded(m_viewAgent, "ContactsAdded"),
402 m_contactsRemoved(m_viewAgent, "ContactsRemoved")
406 * Invokes one of m_contactsModified/Added/Removed. A failure of
407 * the asynchronous call indicates that the client is dead and
408 * that its view can be purged.
410 template<class V> void sendChange(const GDBusCXX::DBusClientCall0 &call,
414 // Changes get aggregated inside handleChange().
415 call.start(getObject(),
418 boost::bind(ViewResource::sendDone,
421 &call == &m_contactsModified ? "ContactsModified()" :
422 &call == &m_contactsAdded ? "ContactsAdded()" :
423 &call == &m_contactsRemoved ? "ContactsRemoved()" : "???",
428 * Merge local changes as much as possible, to avoid excessive
429 * D-Bus traffic. Pending changes get flushed each time the
430 * view reports that it is stable again or contact data
431 * needs to be sent back to the client. Flushing in the second
432 * case is necessary, because otherwise the client will not have
433 * an up-to-date view when the requested data arrives.
435 void handleChange(const GDBusCXX::DBusClientCall0 &call,
436 int start, const IndividualData &data)
438 FolksIndividual *individual = data.m_individual.get();
439 const char *id = folks_individual_get_id(individual);
440 SE_LOG_DEBUG(NULL, "handle change %s: %s, #%d, %s = %s",
442 &call == &m_contactsModified ? "modified" :
443 &call == &m_contactsAdded ? "added" :
444 &call == &m_contactsRemoved ? "remove" : "???",
447 folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual))
450 int pendingCount = m_pendingChange.m_ids.size();
451 if (pendingCount == 0) {
452 // Sending a "contact modified" twice for the same range is redundant.
453 // If the recipient read the data since the last signal, m_lastChange
454 // got cleared and we don't do this optimization.
455 if (m_lastChange.m_call == &m_contactsModified &&
456 &call == &m_contactsModified &&
457 start >= m_lastChange.m_start &&
458 start < m_lastChange.m_start + (int)m_lastChange.m_ids.size()) {
459 SE_LOG_DEBUG(NULL, "handle change %s: redundant 'modified' signal, ignore",
465 // Nothing pending, delay sending.
466 m_pendingChange.m_call = &call;
467 m_pendingChange.m_start = start;
468 m_pendingChange.m_ids.push_back(id);
469 SE_LOG_DEBUG(NULL, "handle change %s: stored as pending change",
474 if (m_pendingChange.m_call == &call) {
475 // Same operation. Can we extend it?
476 if (&call == &m_contactsModified) {
477 // Modification, indices are unchanged.
478 if (start + 1 == m_pendingChange.m_start) {
479 // New modified element at the front.
480 SE_LOG_DEBUG(NULL, "handle change %s: insert modification, #%d + %d and #%d => #%d + %d",
482 m_pendingChange.m_start, pendingCount,
484 m_pendingChange.m_start - 1, pendingCount + 1);
485 m_pendingChange.m_ids.push_front(id);
486 m_pendingChange.m_start--;
488 } else if (start == m_pendingChange.m_start + pendingCount) {
489 // New modified element at the end.
490 SE_LOG_DEBUG(NULL, "handle change %s: append modification, #%d + %d and #%d => #%d + %d",
492 m_pendingChange.m_start, pendingCount,
494 m_pendingChange.m_start, pendingCount + 1);
495 m_pendingChange.m_ids.push_back(id);
497 } else if (start >= m_pendingChange.m_start &&
498 start < m_pendingChange.m_start + pendingCount) {
499 // Element modified again => no change, except perhaps for the ID.
500 SE_LOG_DEBUG(NULL, "handle change %s: modification of already modified contact, #%d + %d and #%d => #%d + %d",
502 m_pendingChange.m_start, pendingCount,
504 m_pendingChange.m_start, pendingCount);
505 m_pendingChange.m_ids[start - m_pendingChange.m_start] = id;
508 } else if (&call == &m_contactsAdded) {
509 // Added individuals. The new start index already includes
510 // the previously added individuals.
511 int newCount = m_pendingChange.m_ids.size() + 1;
512 if (start >= m_pendingChange.m_start) {
513 // Adding in the middle or at the end?
514 int end = m_pendingChange.m_start + pendingCount;
516 SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'added' individuals at end, #%d + %d and #%d new => #%d + %d",
518 m_pendingChange.m_start, pendingCount,
520 m_pendingChange.m_start, newCount);
521 m_pendingChange.m_ids.push_back(id);
523 } else if (start < end) {
524 SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'added' individuals in the middle, #%d + %d and #%d new => #%d + %d",
526 m_pendingChange.m_start, pendingCount,
528 m_pendingChange.m_start, newCount);
529 m_pendingChange.m_ids.insert(m_pendingChange.m_ids.begin() + (start - m_pendingChange.m_start),
534 // Adding directly before the previous start?
535 if (start + 1 == m_pendingChange.m_start) {
536 SE_LOG_DEBUG(NULL, "handle change %s: reduce start and increase count of 'added' individuals, #%d + %d and #%d => #%d + %d",
538 m_pendingChange.m_start, pendingCount,
541 m_pendingChange.m_start = start;
542 m_pendingChange.m_ids.push_front(id);
547 // Removed individuals. The new start was already reduced by
548 // previously removed individuals.
549 int newCount = m_pendingChange.m_ids.size() + 1;
550 if (start == m_pendingChange.m_start) {
551 // Removing directly at end.
552 SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'removed' individuals, #%d + %d and #%d => #%d + %d",
554 m_pendingChange.m_start, pendingCount,
556 m_pendingChange.m_start, newCount);
557 m_pendingChange.m_ids.push_back(id);
559 } else if (start + 1 == m_pendingChange.m_start) {
560 // Removing directly before the previous start.
561 SE_LOG_DEBUG(NULL, "handle change %s: reduce start and increase count of 'removed' individuals, #%d + %d and #%d => #%d + %d",
563 m_pendingChange.m_start, pendingCount,
566 m_pendingChange.m_start = start;
567 m_pendingChange.m_ids.push_front(id);
573 // More complex merging is possible. For example, "removed 1
574 // at #10" and "added 1 at #10" can be turned into "modified 1
575 // at #10", if the ID is the same. This happens when a contact
576 // gets modified and folks decides to recreate the
577 // FolksIndividual instead of modifying it.
578 if (m_pendingChange.m_call == &m_contactsRemoved &&
579 &call == &m_contactsAdded &&
580 start == m_pendingChange.m_start &&
581 1 == m_pendingChange.m_ids.size() &&
582 m_pendingChange.m_ids.front() == id) {
583 SE_LOG_DEBUG(NULL, "handle change %s: removed individual was re-added => #%d modified",
586 m_pendingChange.m_call = &m_contactsModified;
590 // Cannot merge changes.
593 // Now remember requested change.
594 m_pendingChange.m_call = &call;
595 m_pendingChange.m_start = start;
596 m_pendingChange.m_ids.clear();
597 m_pendingChange.m_ids.push_back(id);
600 /** Clear pending state and flush. */
603 int count = m_pendingChange.m_ids.size();
605 SE_LOG_DEBUG(NULL, "send change %s: %s, #%d + %d",
607 m_pendingChange.m_call == &m_contactsModified ? "modified" :
608 m_pendingChange.m_call == &m_contactsAdded ? "added" :
609 m_pendingChange.m_call == &m_contactsRemoved ? "remove" : "???",
610 m_pendingChange.m_start, count);
611 m_lastChange = m_pendingChange;
612 m_pendingChange.m_ids.clear();
613 sendChange(*m_lastChange.m_call,
614 m_lastChange.m_start,
619 /** Current state is stable. Flush and tell agent. */
623 m_quiescent.start(getObject(),
624 boost::bind(ViewResource::sendDone,
632 * Used as callback for sending changes to the ViewAgent. Only
633 * holds weak references and thus does not prevent deleting view
636 static void sendDone(const boost::weak_ptr<ViewResource> &self,
637 const std::string &error,
641 if (required && !error.empty()) {
642 // remove view because it is no longer needed
643 SE_LOG_DEBUG(NULL, "ViewAgent %s method call failed, deleting view: %s", method, error.c_str());
644 boost::shared_ptr<ViewResource> r = self.lock();
651 void init(boost::shared_ptr<ViewResource> self)
655 // activate D-Bus interface
656 add(this, &ViewResource::readContacts, "ReadContacts");
657 add(this, &ViewResource::close, "Close");
658 add(this, &ViewResource::refineSearch, "RefineSearch");
659 add(this, &ViewResource::replaceSearch, "ReplaceSearch");
662 // The view might have been started already, for example when
663 // reconnecting a ViewResource to the persistent full view.
664 // Therefore tell the agent about the current content before
665 // starting, then connect to signals, and finally start.
666 int size = m_view->size();
668 std::vector<std::string> ids;
670 const IndividualData *data;
671 for (int i = 0; i < size; i++) {
672 data = m_view->getContact(i);
673 ids.push_back(folks_individual_get_id(data->m_individual.get()));
675 sendChange(m_contactsAdded, 0, ids);
677 m_view->m_quiescenceSignal.connect(IndividualView::QuiescenceSignal_t::slot_type(&ViewResource::quiescent,
679 m_view->m_modifiedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
681 boost::cref(m_contactsModified),
684 m_view->m_addedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
686 boost::cref(m_contactsAdded),
689 m_view->m_removedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
691 boost::cref(m_contactsRemoved),
696 // start() did all the initial work, no further changes expected
697 // if state is considered stable => tell users.
698 if (m_view->isQuiescent()) {
704 /** returns the integer number that will be used for the next view resource */
705 static unsigned getNextViewNumber() { return m_counter; }
707 static boost::shared_ptr<ViewResource> create(const boost::shared_ptr<IndividualView> &view,
708 const boost::shared_ptr<LocaleFactory> &locale,
709 const boost::shared_ptr<Client> &owner,
710 GDBusCXX::connection_type *connection,
711 const GDBusCXX::Caller_t &ID,
712 const GDBusCXX::DBusObject_t &agentPath)
714 boost::shared_ptr<ViewResource> viewResource(new ViewResource(view,
720 viewResource->init(viewResource);
724 /** ViewControl.ReadContacts() */
725 void readContacts(const std::vector<std::string> &ids, IndividualView::Contacts &contacts)
728 // Ensure that client's view is up-to-date, then prepare the
731 m_view->readContacts(ids, contacts);
732 // Discard the information about the previous 'modified' signal
733 // if the client now has data in that range. Necessary because
734 // otherwise future 'modified' signals for that range might get
735 // suppressed in handleChange().
736 int modifiedCount = m_lastChange.m_ids.size();
737 if (m_lastChange.m_call == &m_contactsModified &&
739 BOOST_FOREACH (const IndividualView::Contacts::value_type &entry, contacts) {
740 int index = entry.first;
741 if (index >= m_lastChange.m_start && index < m_lastChange.m_start + modifiedCount) {
742 m_lastChange.m_ids.clear();
750 /** ViewControl.Close() */
753 // Removing the resource from its owner will drop the last
754 // reference and delete it when we return.
755 boost::shared_ptr<ViewResource> r = m_self.lock();
757 boost::shared_ptr<Client> c = m_owner.lock();
764 /** ViewControl.RefineSearch() */
765 void refineSearch(const std::vector<LocaleFactory::Filter_t> &filterArray)
767 replaceSearch(filterArray, true);
770 void replaceSearch(const std::vector<LocaleFactory::Filter_t> &filterArray, bool refine)
772 // Same as in Search().
773 LocaleFactory::Filter_t filter = filterArray;
774 boost::shared_ptr<IndividualFilter> individualFilter = m_locale->createFilter(filter, 0);
775 m_view->replaceFilter(individualFilter, refine);
778 unsigned int ViewResource::m_counter;
780 void Manager::search(const boost::shared_ptr< GDBusCXX::Result1<GDBusCXX::DBusObject_t> > &result,
781 const GDBusCXX::Caller_t &ID,
782 const boost::shared_ptr<GDBusCXX::Watch> &watch,
783 const std::vector<LocaleFactory::Filter_t> &filterVector,
784 const GDBusCXX::DBusObject_t &agentPath)
786 // TODO: figure out a native, thread-safe API for this.
788 // Start folks in parallel with asking for an ESourceRegistry.
791 // We use a std::vector as outer type to help Python decide how to
792 // send the empty list []. When we declare our parameter as
793 // variant instead of array of variants, as we do now, then the
794 // Python programmer has to use dbus.Array([], signature='s'),
795 // which breaks backwards compatibility (wasn't necessary earlier)
796 // and is not easy to use.
798 // But before we can pass the filter on, we need to turn it into
799 // a variant containing the vector.
800 LocaleFactory::Filter_t filter;
801 filter = filterVector;
803 // We don't know for sure whether we'll need the ESourceRegistry.
804 // Ask for it, just to be sure. If we need to hurry because we are
805 // doing a caller ID lookup during startup, then we'll need it.
806 EDSRegistryLoader::getESourceRegistryAsync(boost::bind(&Manager::searchWithRegistry,
817 void Manager::searchWithRegistry(const ESourceRegistryCXX ®istry,
818 const GError *gerror,
819 const boost::shared_ptr< GDBusCXX::Result1<GDBusCXX::DBusObject_t> > &result,
820 const GDBusCXX::Caller_t &ID,
821 const boost::shared_ptr<GDBusCXX::Watch> &watch,
822 const LocaleFactory::Filter_t &filter,
823 const GDBusCXX::DBusObject_t &agentPath) throw()
827 GErrorCXX::throwError("create ESourceRegistry", gerror);
836 // Tell caller about specific error.
837 dbusErrorCallback(result);
841 void Manager::doSearch(const ESourceRegistryCXX ®istry,
842 const boost::shared_ptr< GDBusCXX::Result1<GDBusCXX::DBusObject_t> > &result,
843 const GDBusCXX::Caller_t &ID,
844 const boost::shared_ptr<GDBusCXX::Watch> &watch,
845 const LocaleFactory::Filter_t &filter,
846 const GDBusCXX::DBusObject_t &agentPath)
848 // Create and track view which is owned by the caller.
849 boost::shared_ptr<Client> client = m_server->addClient(ID, watch);
851 boost::shared_ptr<IndividualView> view;
852 view = m_folks->getMainView();
853 bool quiescent = view->isQuiescent();
854 std::string ebookFilter;
855 // Always use a filtered view. That way we can implement ReplaceView or RefineView
856 // without having to switch from a FullView to a FilteredView.
857 boost::shared_ptr<IndividualFilter> individualFilter = m_locale->createFilter(filter, 0);
858 ebookFilter = individualFilter->getEBookFilter();
860 // Don't search via EDS directly because the unified
861 // address book is ready.
864 view = FilteredView::create(view, individualFilter);
865 view->setName(StringPrintf("filtered view%u", ViewResource::getNextViewNumber()));
867 SE_LOG_DEBUG(NULL, "preparing %s: EDS search term is '%s', active address books %s",
870 boost::join(m_enabledEBooks, " ").c_str());
871 if (!ebookFilter.empty() && !m_enabledEBooks.empty()) {
872 // Set up direct searching in all active address books.
873 // These searches are done once, so don't bother to deal
874 // with future changes to the active address books or
876 MergeView::Searches searches;
877 searches.reserve(m_enabledEBooks.size());
878 boost::shared_ptr<IndividualCompare> compare =
879 m_sortOrder.empty() ?
880 IndividualCompare::defaultCompare() :
881 m_locale->createCompare(m_sortOrder);
883 BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) {
884 searches.push_back(EDSFView::create(registry,
887 searches.back()->setName(StringPrintf("eds view %s %s", uuid.c_str(), ebookFilter.c_str()));
889 boost::shared_ptr<MergeView> merge(MergeView::create(view,
893 merge->setName(StringPrintf("merge view%u", ViewResource::getNextViewNumber()));
897 boost::shared_ptr<ViewResource> viewResource(ViewResource::create(view,
903 client->attach(boost::shared_ptr<Resource>(viewResource));
905 // created local resource
906 result->done(viewResource->getPath());
909 void Manager::runInSession(const std::string &config,
910 Server::SessionFlags flags,
911 const boost::shared_ptr<GDBusCXX::Result> &result,
912 const boost::function<void (const boost::shared_ptr<Session> &session)> &callback)
915 boost::shared_ptr<Session> session = m_server->startInternalSession(config,
917 boost::bind(&Manager::doSession,
922 if (session->getSyncStatus() == Session::SYNC_QUEUEING) {
923 // Must continue to wait instead of dropping the last reference.
924 m_pending.push_back(std::make_pair(result, session));
927 // Tell caller about specific error.
928 dbusErrorCallback(result);
932 void Manager::doSession(const boost::weak_ptr<Session> &weakSession,
933 const boost::shared_ptr<GDBusCXX::Result> &result,
934 const boost::function<void (const boost::shared_ptr<Session> &session)> &callback)
937 boost::shared_ptr<Session> session = weakSession.lock();
939 // Destroyed already?
942 // Drop permanent reference, session will be destroyed when we
944 m_pending.remove(std::make_pair(result, session));
946 // Now run the operation.
949 // Tell caller about specific error.
950 dbusErrorCallback(result);
954 void Manager::getActiveAddressBooks(std::vector<std::string> &dbIDs)
956 BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) {
957 if (uuid == "system-address-book") {
960 dbIDs.push_back(DB_PEER_PREFIX + uuid.substr(strlen(MANAGER_PREFIX)));
965 void Manager::setActiveAddressBooks(const std::vector<std::string> &dbIDs)
967 // Build list of EDS UUIDs.
968 std::set<std::string> uuids;
969 BOOST_FOREACH (const std::string &dbID, dbIDs) {
970 if (boost::starts_with(dbID, DB_PEER_PREFIX)) {
971 // Database of a specific peer.
972 std::string uid = dbID.substr(strlen(DB_PEER_PREFIX));
973 uuids.insert(MANAGER_PREFIX + uid);
974 } else if (dbID.empty()) {
975 // System database. It's UUID is hard-coded here because
976 // it is fixed in practice and managing an ESourceRegistry
977 // just to get the value seems overkill.
978 uuids.insert("system-address-book");
980 SE_THROW("invalid address book ID: " + dbID);
984 // Swap and set in aggregator.
985 std::swap(uuids, m_enabledEBooks);
987 m_configNode->writeProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY,
988 InitStateString(boost::join(m_enabledEBooks, " "), true));
989 m_configNode->flush();
992 void Manager::initDatabases()
994 m_folks->setDatabases(m_enabledEBooks);
997 static void checkPeerUID(const std::string &uid)
999 const pcrecpp::RE re("[-a-z0-9]*");
1000 if (!re.FullMatch(uid)) {
1001 SE_THROW(StringPrintf("invalid peer uid: %s", uid.c_str()));
1005 void Manager::createPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1006 const std::string &uid, const StringMap &properties)
1008 setPeer(result, uid, properties, CREATE_PEER);
1011 void Manager::modifyPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1012 const std::string &uid, const StringMap &properties)
1014 setPeer(result, uid, properties, SET_PEER);
1017 void Manager::setPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1018 const std::string &uid, const StringMap &properties,
1022 runInSession(StringPrintf("@%s%s",
1025 Server::SESSION_FLAG_NO_SYNC,
1027 boost::bind(&Manager::doSetPeer, this, _1, result, uid, properties, mode));
1030 static const char * const PEER_KEY_PROTOCOL = "protocol";
1031 static const char * const PEER_SYNCML_PROTOCOL = "SyncML";
1032 static const char * const PEER_PBAP_PROTOCOL = "PBAP";
1033 static const char * const PEER_FILES_PROTOCOL = "files";
1034 static const char * const PEER_KEY_TRANSPORT = "transport";
1035 static const char * const PEER_BLUETOOTH_TRANSPORT = "Bluetooth";
1036 static const char * const PEER_IP_TRANSPORT = "IP";
1037 static const char * const PEER_DEF_TRANSPORT = PEER_BLUETOOTH_TRANSPORT;
1038 static const char * const PEER_KEY_ADDRESS = "address";
1039 static const char * const PEER_KEY_DATABASE = "database";
1040 static const char * const PEER_KEY_LOGDIR = "logdir";
1041 static const char * const PEER_KEY_MAXSESSIONS = "maxsessions";
1043 static std::string GetEssential(const StringMap &properties, const char *key,
1044 bool allowEmpty = false)
1046 InitStateString entry = GetWithDef(properties, key);
1047 if (!entry.wasSet() ||
1048 (!allowEmpty && entry.empty())) {
1049 SE_THROW(StringPrintf("peer config: '%s' must be set%s",
1051 allowEmpty ? "" : " to a non-empty value"));
1056 void Manager::doSetPeer(const boost::shared_ptr<Session> &session,
1057 const boost::shared_ptr<GDBusCXX::Result0> &result,
1058 const std::string &uid, const StringMap &properties,
1061 // The session is active now, we have exclusive control over the
1062 // databases and the config. Create or update config.
1063 std::string protocol = GetEssential(properties, PEER_KEY_PROTOCOL);
1064 std::string transport = GetWithDef(properties, PEER_KEY_TRANSPORT, PEER_DEF_TRANSPORT);
1065 std::string address = GetEssential(properties, PEER_KEY_ADDRESS);
1066 std::string database = GetWithDef(properties, PEER_KEY_DATABASE);
1067 std::string logdir = GetWithDef(properties, PEER_KEY_LOGDIR);
1068 std::string maxsessions = GetWithDef(properties, PEER_KEY_MAXSESSIONS);
1069 unsigned maxLogDirs = 0;
1070 if (!maxsessions.empty()) {
1071 // https://svn.boost.org/trac/boost/ticket/5494
1072 if (boost::starts_with(maxsessions, "-")) {
1073 SE_THROW(StringPrintf("negative 'maxsessions' not allowed: %s", maxsessions.c_str()));
1075 maxLogDirs = boost::lexical_cast<unsigned>(maxsessions);
1078 std::string localDatabaseName = MANAGER_PREFIX + uid;
1079 std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str());
1082 SE_LOG_DEBUG(NULL, "%s: creating config for protocol %s",
1086 if (protocol == PEER_PBAP_PROTOCOL) {
1087 if (!database.empty()) {
1088 SE_THROW(StringPrintf("peer config: %s=%s: choosing database not supported for %s=%s",
1089 PEER_KEY_ADDRESS, address.c_str(),
1090 PEER_KEY_PROTOCOL, protocol.c_str()));
1092 if (transport != PEER_BLUETOOTH_TRANSPORT) {
1093 SE_THROW(StringPrintf("peer config: %s=%s: only transport %s is supported for %s=%s",
1094 PEER_KEY_TRANSPORT, transport.c_str(),
1095 PEER_BLUETOOTH_TRANSPORT,
1096 PEER_KEY_PROTOCOL, protocol.c_str()));
1100 if (protocol == PEER_PBAP_PROTOCOL ||
1101 protocol == PEER_FILES_PROTOCOL) {
1102 // Create, modify or set local config.
1103 boost::shared_ptr<SyncConfig> config(new SyncConfig(MANAGER_LOCAL_CONFIG + context));
1106 if (config->exists()) {
1107 // When creating a config, the uid must not be in use already.
1108 result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ALREADY_EXISTS, StringPrintf("uid %s is already in use", uid.c_str())));
1113 if (!config->exists()) {
1114 // Modifying expects the config to exist already.
1115 result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_NOT_FOUND, StringPrintf("uid %s is not in use", uid.c_str())));
1120 // May or may not exist, doesn't matter.
1124 config->setDefaults();
1125 config->prepareConfigForWrite();
1126 config->setPreventSlowSync(false);
1127 config->setSyncURL("local://" + context);
1128 config->setPeerIsClient(true);
1129 config->setDumpData(false);
1130 config->setPrintChanges(false);
1131 if (!logdir.empty()) {
1132 config->setLogDir(logdir);
1134 if (!maxsessions.empty()) {
1135 config->setMaxLogDirs(maxLogDirs);
1137 config->setLogLevel(atoi(getEnv("SYNCEVOLUTION_LOGLEVEL", "0")));
1138 boost::shared_ptr<PersistentSyncSourceConfig> source(config->getSyncSourceConfig(MANAGER_LOCAL_SOURCE));
1139 source->setBackend("evolution-contacts");
1140 source->setDatabaseID(localDatabaseName);
1141 source->setSync("local-cache");
1142 source->setURI(MANAGER_REMOTE_SOURCE);
1144 // Ensure that database exists.
1145 SyncSourceParams params(MANAGER_LOCAL_SOURCE,
1146 config->getSyncSourceNodes(MANAGER_LOCAL_SOURCE),
1149 boost::scoped_ptr<SyncSource> syncSource(SyncSource::createSource(params));
1150 SyncSource::Databases databases = syncSource->getDatabases();
1152 BOOST_FOREACH (const SyncSource::Database &database, databases) {
1153 if (database.m_uri == localDatabaseName) {
1159 syncSource->createDatabase(SyncSource::Database(localDatabaseName, localDatabaseName));
1162 // Now also create target config, in the same context.
1163 config.reset(new SyncConfig(MANAGER_REMOTE_CONFIG + context));
1164 config->setDefaults();
1165 config->prepareConfigForWrite();
1166 config->setPreventSlowSync(false);
1167 config->setDumpData(false);
1168 config->setPrintChanges(false);
1169 if (!logdir.empty()) {
1170 config->setLogDir(logdir);
1172 config->setLogLevel(atoi(getEnv("SYNCEVOLUTION_LOGLEVEL", "0")));
1173 if (!maxsessions.empty()) {
1174 config->setMaxLogDirs(maxLogDirs);
1176 source = config->getSyncSourceConfig(MANAGER_REMOTE_SOURCE);
1177 if (protocol == PEER_PBAP_PROTOCOL) {
1179 source->setDatabaseID("obex-bt://" + address);
1180 source->setBackend("pbap");
1182 // Local sync with files on the target side.
1183 // Format is hard-coded to vCard 3.0.
1184 source->setDatabaseID("file://" + address);
1185 source->setDatabaseFormat("text/vcard");
1186 source->setBackend("file");
1190 SE_THROW(StringPrintf("peer config: %s=%s not supported",
1196 SE_LOG_DEBUG(NULL, "%s: created config for protocol %s",
1202 Manager::PeersMap Manager::getAllPeers()
1206 SyncConfig::ConfigList configs = SyncConfig::getConfigs();
1207 std::string prefix = StringPrintf("%s@%s",
1208 MANAGER_LOCAL_CONFIG,
1211 BOOST_FOREACH (const StringPair &entry, configs) {
1212 if (boost::starts_with(entry.first, prefix)) {
1213 // One of our configs.
1214 std::string uid = entry.first.substr(prefix.size());
1215 StringMap &properties = peers[uid];
1216 // Extract relevant properties from configs.
1217 SyncConfig localConfig(entry.first);
1218 InitState< std::vector<std::string> > syncURLs = localConfig.getSyncURL();
1219 std::string syncURL;
1220 if (!syncURLs.empty()) {
1221 syncURL = syncURLs[0];
1223 if (boost::starts_with(syncURL, "local://")) {
1224 // Look at target source to determine protocol.
1225 SyncConfig targetConfig(StringPrintf("%s@%s%s",
1226 MANAGER_REMOTE_CONFIG,
1229 boost::shared_ptr<PersistentSyncSourceConfig> source(targetConfig.getSyncSourceConfig(MANAGER_REMOTE_SOURCE));
1230 std::string backend = source->getBackend();
1231 std::string database = source->getDatabaseID();
1232 if (backend == "PBAP Address Book") {
1233 properties[PEER_KEY_PROTOCOL] = PEER_PBAP_PROTOCOL;
1234 if (boost::starts_with(database, "obex-bt://")) {
1235 properties[PEER_KEY_ADDRESS] = database.substr(strlen("obex-bt://"));
1237 } else if (backend == "file") {
1238 properties[PEER_KEY_PROTOCOL] = PEER_FILES_PROTOCOL;
1239 if (boost::starts_with(database, "file://")) {
1240 properties[PEER_KEY_ADDRESS] = database.substr(strlen("file://"));
1251 void Manager::removePeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1252 const std::string &uid)
1255 runInSession(StringPrintf("@%s%s",
1258 Server::SESSION_FLAG_NO_SYNC,
1260 boost::bind(&Manager::doRemovePeer, this, _1, result, uid));
1263 void Manager::doRemovePeer(const boost::shared_ptr<Session> &session,
1264 const boost::shared_ptr<GDBusCXX::Result0> &result,
1265 const std::string &uid)
1267 std::string localDatabaseName = MANAGER_PREFIX + uid;
1268 std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str());
1270 // Remove database. This is expected to be noticed by libfolks
1271 // once we delete the database without us having to tell it, but
1272 // doing so doesn't hurt.
1273 m_enabledEBooks.erase(localDatabaseName);
1275 m_configNode->writeProperty(MANAGER_CONFIG_SORT_PROPERTY,
1276 InitStateString(boost::join(m_enabledEBooks, " "), true));
1277 m_configNode->flush();
1279 // Access config via context (includes sync and target config).
1280 boost::shared_ptr<SyncConfig> config(new SyncConfig(context));
1282 // Remove database, if it exists.
1283 if (config->exists(CONFIG_LEVEL_CONTEXT)) {
1284 boost::shared_ptr<PersistentSyncSourceConfig> source(config->getSyncSourceConfig(MANAGER_LOCAL_SOURCE));
1285 SyncSourceNodes nodes = config->getSyncSourceNodes(MANAGER_LOCAL_SOURCE);
1286 if (nodes.dataConfigExists()) {
1287 SyncSourceParams params(MANAGER_LOCAL_SOURCE,
1291 boost::scoped_ptr<SyncSource> syncSource(SyncSource::createSource(params));
1292 SyncSource::Databases databases = syncSource->getDatabases();
1294 BOOST_FOREACH (const SyncSource::Database &database, databases) {
1295 if (database.m_uri == localDatabaseName) {
1301 syncSource->deleteDatabase(localDatabaseName, SyncSource::REMOVE_DATA_FORCE);
1306 // Remove entire context, just in case. Placing the code here also
1307 // ensures that nothing except the config itself has the config
1308 // nodes open, which would prevent removing them. For the same
1309 // reason the SyncConfig is recreated: to clear all references to
1310 // sources that were opened via it.
1311 config.reset(new SyncConfig(context));
1319 void Manager::syncPeer(const boost::shared_ptr<GDBusCXX::Result1<SyncResult> > &result,
1320 const std::string &uid)
1323 runInSession(StringPrintf("%s@%s%s",
1324 MANAGER_LOCAL_CONFIG,
1327 Server::SESSION_FLAG_NO_SYNC,
1329 boost::bind(&Manager::doSyncPeer, this, _1, result, uid));
1332 static Manager::SyncResult SyncReport2Result(const SyncReport &report)
1334 Manager::SyncResult result;
1335 int added = 0, updated = 0, removed = 0;
1336 if (!report.empty()) {
1337 const SyncSourceReport &source = report.begin()->second;
1338 added = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL);
1339 updated = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL);
1340 removed = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL);
1342 result["modified"] = added || updated || removed;
1343 result["added"] = added;
1344 result["updated"] = updated;
1345 result["removed"] = removed;
1349 static void doneSyncPeer(const boost::shared_ptr<GDBusCXX::Result1<Manager::SyncResult> > &result,
1350 SyncMLStatus status,
1351 const SyncReport &report)
1353 if (status == STATUS_OK ||
1354 status == STATUS_HTTP_OK) {
1355 result->done(SyncReport2Result(report));
1356 } else if (status == (SyncMLStatus)sysync::LOCERR_USERABORT) {
1357 result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ABORTED, "running sync aborted, probably by StopSync()"));
1359 result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_BAD_STATUS, Status2String(status)));
1363 void Manager::doSyncPeer(const boost::shared_ptr<Session> &session,
1364 const boost::shared_ptr<GDBusCXX::Result1<SyncResult> > &result,
1365 const std::string &uid)
1367 // Keep client informed about progress.
1368 emitSyncProgress(uid, "started", SyncResult());
1369 session->m_doneSignal.connect(boost::bind(boost::ref(emitSyncProgress), uid, "done", SyncResult()));
1370 session->m_sourceSynced.connect(boost::bind(&Manager::report2SyncProgress, m_self, uid, _1, _2));
1372 // Determine sync mode. "pbap" is valid only when the remote
1373 // source uses the PBAP backend. Otherwise we use "ephemeral",
1374 // which ensures that absolutely no sync meta data gets written.
1375 std::string syncMode = "ephemeral";
1376 std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str());
1377 boost::shared_ptr<SyncConfig> config(new SyncConfig(MANAGER_REMOTE_CONFIG + context));
1378 boost::shared_ptr<PersistentSyncSourceConfig> source(config->getSyncSourceConfig(MANAGER_REMOTE_SOURCE));
1379 if (source->getBackend() == "PBAP Address Book") {
1383 // After sync(), the session is tracked as the active sync session
1384 // by the server. It was removed from our own m_pending list by
1386 session->sync(syncMode, SessionCommon::SourceModes_t());
1387 // Relay result to caller when done.
1388 session->m_doneSignal.connect(boost::bind(doneSyncPeer, result, _1, _2));
1391 void Manager::report2SyncProgress(const std::string &uid,
1392 const std::string &sourceName,
1393 const SyncSourceReport &source)
1396 report.addSyncSourceReport("foo", source);
1397 emitSyncProgress(uid, "modified", SyncReport2Result(report));
1400 void Manager::stopSync(const boost::shared_ptr<GDBusCXX::Result0> &result,
1401 const std::string &uid)
1405 // Fully qualified peer config name. Only used for sync sessions
1406 // and thus good enough to identify them.
1407 std::string syncConfigName = StringPrintf("%s@%s%s",
1408 MANAGER_LOCAL_CONFIG,
1412 // Remove all pending sessions of the peer. Make a complete
1413 // copy of the list, to avoid issues with modifications of the
1414 // underlying list while we iterate over it.
1415 BOOST_FOREACH (const Pending_t::value_type &entry, Pending_t(m_pending)) {
1416 std::string configName = entry.second->getConfigName();
1417 if (configName == syncConfigName) {
1418 entry.first->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ABORTED, "pending sync aborted by StopSync()"));
1419 m_pending.remove(entry);
1423 // Stop the currently running sync if it is for the peer.
1424 // It may or may not complete, depending on what it is currently
1425 // doing. We'll check in doneSyncPeer().
1426 boost::shared_ptr<Session> session = m_server->getSyncSession();
1427 bool aborting = false;
1429 std::string configName = session->getConfigName();
1430 if (configName == syncConfigName) {
1431 // Return to caller later, when aborting is done.
1432 session->abortAsync(SimpleResult(boost::bind(&GDBusCXX::Result0::done, result),
1433 createDBusErrorCb(result)));
1442 void Manager::addContact(const boost::shared_ptr< GDBusCXX::Result1<std::string> > &result,
1443 const std::string &addressbook,
1444 const PersonaDetails &details)
1447 if (!addressbook.empty()) {
1448 SE_THROW("only the system address book is writable");
1450 m_folks->addContact(createDBusCb(result), details);
1452 dbusErrorCallback(result);
1456 void Manager::modifyContact(const boost::shared_ptr<GDBusCXX::Result0> &result,
1457 const std::string &addressbook,
1458 const std::string &localID,
1459 const PersonaDetails &details)
1462 if (!addressbook.empty()) {
1463 SE_THROW("only the system address book is writable");
1465 m_folks->modifyContact(createDBusCb(result),
1469 dbusErrorCallback(result);
1473 void Manager::removeContact(const boost::shared_ptr<GDBusCXX::Result0> &result,
1474 const std::string &addressbook,
1475 const std::string &localID)
1478 if (!addressbook.empty()) {
1479 SE_THROW("only the system address book is writable");
1481 m_folks->removeContact(createDBusCb(result),
1484 dbusErrorCallback(result);