Imported Upstream version 1.3.99.4
[platform/upstream/syncevolution.git] / src / dbus / server / pim / manager.cpp
1 /*
2  * Copyright (C) 2012 Intel Corporation
3  *
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.
8  *
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.
13  *
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
17  * 02110-1301  USA
18  */
19
20 #include "manager.h"
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"
30
31 #include <syncevo/IniConfigNode.h>
32 #include <syncevo/BoostHelper.h>
33
34 #include <boost/lexical_cast.hpp>
35 #include <boost/scoped_ptr.hpp>
36 #include <boost/tokenizer.hpp>
37 #include <deque>
38
39 #include <pcrecpp.h>
40
41 SE_BEGIN_CXX
42
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";
52
53 static const char * const MANAGER_CONFIG_SORT_PROPERTY = "sort";
54 static const char * const MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY = "active";
55
56 /**
57  * Prefix for peer databases ("peer-<uid>")
58  */
59 static const char * const DB_PEER_PREFIX = "peer-";
60
61 /**
62  * Name prefix for SyncEvolution config contexts used by PIM manager.
63  * Used in combination with the uid string provided by the PIM manager
64  * client, like this:
65  *
66  * eds@pim-manager-<uid> source 'eds' syncs with target-config@pim-manager-<uid>
67  * source 'remote' for PBAP.
68  *
69  * eds@pim-manager-<uid> source 'local' syncs with a SyncML peer directly.
70  */
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";
76
77 Manager::Manager(const boost::shared_ptr<Server> &server) :
78     DBusObjectHelper(server->getConnection(),
79                      MANAGER_PATH,
80                      MANAGER_IFACE),
81     m_mainThread(g_thread_self()),
82     m_server(server),
83     m_locale(LocaleFactory::createFactory()),
84     emitSyncProgress(*this, "SyncProgress")
85 {
86 }
87
88 Manager::~Manager()
89 {
90     // Clear the pending queue before self-desctructing, because the
91     // entries hold pointers to this instance.
92     m_pending.clear();
93     if (m_preventingAutoTerm) {
94         m_server->autoTermUnref();
95     }
96 }
97
98 #ifdef PIM_MANAGER_TEST_THREADING
99 static gpointer StartManager(gpointer data)
100 {
101     Manager *manager = static_cast<Manager *>(data);
102     manager->start();
103     return NULL;
104 }
105 #endif
106
107 void Manager::init()
108 {
109     // Restore sort order and active databases.
110     m_configNode.reset(new IniFileConfigNode(SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution"),
111                                              "pim-manager.ini",
112                                              false));
113     InitStateString order = m_configNode->readProperty(MANAGER_CONFIG_SORT_PROPERTY);
114     m_sortOrder = order.wasSet() ?
115         order :
116         "last/first";
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);
124         }
125     }
126     initFolks();
127     initSorting(m_sortOrder);
128     initDatabases();
129
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);
148
149     // Ready, make it visible via D-Bus.
150     activate();
151
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)>());
156
157 #ifdef PIM_MANAGER_TEST_THREADING
158     GThread *thread = g_thread_new("start",
159                                    StartManager,
160                                    this);
161     g_thread_unref(thread);
162 #endif
163 }
164
165 struct TaskForMain
166 {
167     GMutex m_mutex;
168     GCond m_cond;
169     bool m_done;
170     boost::function<void ()> m_operation;
171     boost::function<void ()> m_rethrow;
172
173     void runTaskOnIdle()
174     {
175         g_mutex_lock(&m_mutex);
176
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.
181         try {
182             m_operation();
183         } catch (...) {
184             std::string explanation;
185             Exception::handle(explanation);
186             m_rethrow = boost::bind(Exception::tryRethrow, explanation, true);
187         }
188
189         // Wake up task.
190         m_done = true;
191         g_cond_signal(&m_cond);
192         g_mutex_unlock(&m_mutex);
193     }
194 };
195
196 template <class R> void AssignResult(const boost::function<R ()> &operation,
197                                      R &res)
198 {
199     res = operation();
200 }
201
202 template <class R> R Manager::runInMainRes(const boost::function<R ()> &operation)
203 {
204     // Prepare task.
205     R res;
206     TaskForMain task;
207     g_mutex_init(&task.m_mutex);
208     g_cond_init(&task.m_cond);
209     task.m_done = false;
210     task.m_operation = boost::bind(&AssignResult<R>, boost::cref(operation), boost::ref(res));
211
212     // Run in main.
213     Timeout timeout;
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);
219     }
220     g_mutex_unlock(&task.m_mutex);
221
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) {
226         task.m_rethrow();
227     }
228     return res;
229 }
230
231 static int Return1(const boost::function<void ()> &operation)
232 {
233     operation();
234     return 1;
235 }
236
237 void Manager::runInMainVoid(const boost::function<void ()> &operation)
238 {
239     runInMainRes<int>(boost::bind(Return1, boost::cref(operation)));
240 }
241
242 void Manager::initFolks()
243 {
244     m_folks = IndividualAggregator::create(m_locale);
245 }
246
247 void Manager::initSorting(const std::string &order)
248 {
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 =
254         order.empty() ?
255         IndividualCompare::defaultCompare() :
256         m_locale->createCompare(order);
257     m_folks->setCompare(compare);
258 }
259
260 boost::shared_ptr<Manager> Manager::create(const boost::shared_ptr<Server> &server)
261 {
262     boost::shared_ptr<Manager> manager(new Manager(server));
263     manager->m_self = manager;
264     manager->init();
265     return manager;
266 }
267
268 boost::shared_ptr<GDBusCXX::DBusObjectHelper> CreateContactManager(const boost::shared_ptr<Server> &server, bool start)
269 {
270     boost::shared_ptr<Manager> manager = Manager::create(server);
271     if (start) {
272         manager->start();
273     }
274     return manager;
275 }
276
277 void Manager::start()
278 {
279     if (!isMain()) {
280         runInMainV(&Manager::start);
281         return;
282     }
283
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;
289     }
290     m_folks->start();
291 }
292
293 void Manager::stop()
294 {
295     if (!isMain()) {
296         runInMainV(&Manager::stop);
297         return;
298     }
299
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()");
306         initFolks();
307         initDatabases();
308         initSorting(m_sortOrder);
309
310         if (m_preventingAutoTerm) {
311             // Allow auto shutdown again.
312             m_server->autoTermUnref();
313             m_preventingAutoTerm = false;
314         }
315     }
316 }
317
318 bool Manager::isRunning()
319 {
320     if (!isMain()) {
321         return runInMainR(&Manager::isRunning);
322     }
323
324     return m_folks->isRunning();
325 }
326
327 void Manager::setSortOrder(const std::string &order)
328 {
329     if (!isMain()) {
330         runInMainV(&Manager::setSortOrder, order);
331         return;
332     }
333
334     if (order == getSortOrder()) {
335         // Nothing to do.
336         return;
337     }
338
339     // String is checked as part of initSorting,
340     // only store if parsing succeeds.
341     initSorting(order);
342     m_configNode->writeProperty(MANAGER_CONFIG_SORT_PROPERTY, InitStateString(order, true));
343     m_configNode->flush();
344     m_sortOrder = order;
345 }
346
347 std::string Manager::getSortOrder()
348 {
349     if (!isMain()) {
350         return runInMainR(&Manager::getSortOrder);
351     }
352
353     return m_sortOrder;
354 }
355
356 /**
357  * Connects a normal IndividualView to a D-Bus client.
358  * Provides the org.01.pim.contacts.ViewControl API.
359  */
360 class ViewResource : public Resource, public GDBusCXX::DBusObjectHelper
361 {
362     static unsigned int m_counter;
363
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;
369     struct Change {
370         Change() : m_start(0), m_call(NULL) {}
371
372         int m_start;
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,
378         m_contactsAdded,
379         m_contactsRemoved;
380
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++),
389                                    CONTROL_IFACE),
390         m_viewAgent(connection,
391                     agentPath,
392                     AGENT_IFACE,
393                     ID),
394         m_view(view),
395         m_locale(locale),
396         m_owner(owner),
397
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")
403     {}
404
405     /**
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.
409      */
410     template<class V> void sendChange(const GDBusCXX::DBusClientCall0 &call,
411                                       int start,
412                                       const V &ids)
413     {
414         // Changes get aggregated inside handleChange().
415         call.start(getObject(),
416                    start,
417                    ids,
418                    boost::bind(ViewResource::sendDone,
419                                m_self,
420                                _1,
421                                &call == &m_contactsModified ? "ContactsModified()" :
422                                &call == &m_contactsAdded ? "ContactsAdded()" :
423                                &call == &m_contactsRemoved ? "ContactsRemoved()" : "???",
424                                true));
425     }
426
427     /**
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.
434      */
435     void handleChange(const GDBusCXX::DBusClientCall0 &call,
436                       int start, const IndividualData &data)
437     {
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",
441                      getPath(),
442                      &call == &m_contactsModified ? "modified" :
443                      &call == &m_contactsAdded ? "added" :
444                      &call == &m_contactsRemoved ? "remove" : "???",
445                      start,
446                      id,
447                      folks_name_details_get_full_name(FOLKS_NAME_DETAILS(individual))
448                      );
449
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",
460                              getPath());
461
462                 return;
463             }
464
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",
470                          getPath());
471             return;
472         }
473
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",
481                                  getPath(),
482                                  m_pendingChange.m_start, pendingCount,
483                                  start,
484                                  m_pendingChange.m_start - 1, pendingCount + 1);
485                     m_pendingChange.m_ids.push_front(id);
486                     m_pendingChange.m_start--;
487                     return;
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",
491                                  getPath(),
492                                  m_pendingChange.m_start, pendingCount,
493                                  start,
494                                  m_pendingChange.m_start, pendingCount + 1);
495                     m_pendingChange.m_ids.push_back(id);
496                     return;
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",
501                                  getPath(),
502                                  m_pendingChange.m_start, pendingCount,
503                                  start,
504                                  m_pendingChange.m_start, pendingCount);
505                     m_pendingChange.m_ids[start - m_pendingChange.m_start] = id;
506                     return;
507                 }
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;
515                     if (start == end) {
516                         SE_LOG_DEBUG(NULL, "handle change %s: increase count of 'added' individuals at end, #%d + %d and #%d new => #%d + %d",
517                                      getPath(),
518                                      m_pendingChange.m_start, pendingCount,
519                                      start,
520                                      m_pendingChange.m_start, newCount);
521                         m_pendingChange.m_ids.push_back(id);
522                         return;
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",
525                                      getPath(),
526                                      m_pendingChange.m_start, pendingCount,
527                                      start,
528                                      m_pendingChange.m_start, newCount);
529                         m_pendingChange.m_ids.insert(m_pendingChange.m_ids.begin() + (start - m_pendingChange.m_start),
530                                                      id);
531                         return;
532                     }
533                 } else {
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",
537                                      getPath(),
538                                      m_pendingChange.m_start, pendingCount,
539                                      start,
540                                      start, newCount);
541                         m_pendingChange.m_start = start;
542                         m_pendingChange.m_ids.push_front(id);
543                         return;
544                     }
545                 }
546             } else {
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",
553                                  getPath(),
554                                  m_pendingChange.m_start, pendingCount,
555                                  start,
556                                  m_pendingChange.m_start, newCount);
557                     m_pendingChange.m_ids.push_back(id);
558                     return;
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",
562                                  getPath(),
563                                  m_pendingChange.m_start, pendingCount,
564                                  start,
565                                  start, newCount);
566                     m_pendingChange.m_start = start;
567                     m_pendingChange.m_ids.push_front(id);
568                     return;
569                 }
570             }
571         }
572
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",
584                          getPath(),
585                          start);
586             m_pendingChange.m_call = &m_contactsModified;
587             return;
588         }
589
590         // Cannot merge changes.
591         flushChanges();
592
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);
598     }
599
600     /** Clear pending state and flush. */
601     void flushChanges()
602     {
603         int count = m_pendingChange.m_ids.size();
604         if (count) {
605             SE_LOG_DEBUG(NULL, "send change %s: %s, #%d + %d",
606                          getPath(),
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,
615                        m_lastChange.m_ids);
616         }
617     }
618
619     /** Current state is stable. Flush and tell agent. */
620     void quiescent()
621     {
622         flushChanges();
623         m_quiescent.start(getObject(),
624                           boost::bind(ViewResource::sendDone,
625                                       m_self,
626                                       _1,
627                                       "Quiescent()",
628                                       false));
629     }
630
631     /**
632      * Used as callback for sending changes to the ViewAgent. Only
633      * holds weak references and thus does not prevent deleting view
634      * or client.
635      */
636     static void sendDone(const boost::weak_ptr<ViewResource> &self,
637                          const std::string &error,
638                          const char *method,
639                          bool required)
640     {
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();
645             if (r) {
646                 r->close();
647             }
648         }
649     }
650
651     void init(boost::shared_ptr<ViewResource> self)
652     {
653         m_self = self;
654
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");
660         activate();
661
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();
667         if (size) {
668             std::vector<std::string> ids;
669             ids.reserve(size);
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()));
674             }
675             sendChange(m_contactsAdded, 0, ids);
676         }
677         m_view->m_quiescenceSignal.connect(IndividualView::QuiescenceSignal_t::slot_type(&ViewResource::quiescent,
678                                                                                          this).track(self));
679         m_view->m_modifiedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
680                                                                                    this,
681                                                                                    boost::cref(m_contactsModified),
682                                                                                    _1,
683                                                                                    _2).track(self));
684         m_view->m_addedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
685                                                                                 this,
686                                                                                 boost::cref(m_contactsAdded),
687                                                                                 _1,
688                                                                                 _2).track(self));
689         m_view->m_removedSignal.connect(IndividualView::ChangeSignal_t::slot_type(&ViewResource::handleChange,
690                                                                                   this,
691                                                                                   boost::cref(m_contactsRemoved),
692                                                                                   _1,
693                                                                                   _2).track(self));
694         m_view->start();
695
696         // start() did all the initial work, no further changes expected
697         // if state is considered stable => tell users.
698         if (m_view->isQuiescent()) {
699             quiescent();
700         }
701     }
702
703 public:
704     /** returns the integer number that will be used for the next view resource */
705     static unsigned getNextViewNumber() { return m_counter; }
706
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)
713     {
714         boost::shared_ptr<ViewResource> viewResource(new ViewResource(view,
715                                                                       locale,
716                                                                       owner,
717                                                                       connection,
718                                                                       ID,
719                                                                       agentPath));
720         viewResource->init(viewResource);
721         return viewResource;
722     }
723
724     /** ViewControl.ReadContacts() */
725     void readContacts(const std::vector<std::string> &ids, IndividualView::Contacts &contacts)
726     {
727         if (!ids.empty()) {
728             // Ensure that client's view is up-to-date, then prepare the
729             // data for it.
730             flushChanges();
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 &&
738                 modifiedCount) {
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();
743                         break;
744                     }
745                 }
746             }
747         }
748     }
749
750     /** ViewControl.Close() */
751     void close()
752     {
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();
756         if (r) {
757             boost::shared_ptr<Client> c = m_owner.lock();
758             if (c) {
759                 c->detach(r.get());
760             }
761         }
762     }
763
764     /** ViewControl.RefineSearch() */
765     void refineSearch(const std::vector<LocaleFactory::Filter_t> &filterArray)
766     {
767         replaceSearch(filterArray, true);
768     }
769
770     void replaceSearch(const std::vector<LocaleFactory::Filter_t> &filterArray, bool refine)
771     {
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);
776     }
777 };
778 unsigned int ViewResource::m_counter;
779
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)
785 {
786     // TODO: figure out a native, thread-safe API for this.
787
788     // Start folks in parallel with asking for an ESourceRegistry.
789     start();
790
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.
797     //
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;
802
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,
807                                                            m_self,
808                                                            _1,
809                                                            _2,
810                                                            result,
811                                                            ID,
812                                                            watch,
813                                                            filter,
814                                                            agentPath));
815 }
816
817 void Manager::searchWithRegistry(const ESourceRegistryCXX &registry,
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()
824 {
825     try {
826         if (!registry) {
827             GErrorCXX::throwError("create ESourceRegistry", gerror);
828         }
829         doSearch(registry,
830                  result,
831                  ID,
832                  watch,
833                  filter,
834                  agentPath);
835     } catch (...) {
836         // Tell caller about specific error.
837         dbusErrorCallback(result);
838     }
839 }
840
841 void Manager::doSearch(const ESourceRegistryCXX &registry,
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)
847 {
848     // Create and track view which is owned by the caller.
849     boost::shared_ptr<Client> client = m_server->addClient(ID, watch);
850
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();
859     if (quiescent) {
860         // Don't search via EDS directly because the unified
861         // address book is ready.
862         ebookFilter.clear();
863     }
864     view = FilteredView::create(view, individualFilter);
865     view->setName(StringPrintf("filtered view%u", ViewResource::getNextViewNumber()));
866
867     SE_LOG_DEBUG(NULL, "preparing %s: EDS search term is '%s', active address books %s",
868                  view->getName(),
869                  ebookFilter.c_str(),
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
875         // the sort order.
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);
882
883         BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) {
884             searches.push_back(EDSFView::create(registry,
885                                                 uuid,
886                                                 ebookFilter));
887             searches.back()->setName(StringPrintf("eds view %s %s", uuid.c_str(), ebookFilter.c_str()));
888         }
889         boost::shared_ptr<MergeView> merge(MergeView::create(view,
890                                                              searches,
891                                                              m_locale,
892                                                              compare));
893         merge->setName(StringPrintf("merge view%u", ViewResource::getNextViewNumber()));
894         view = merge;
895     }
896
897     boost::shared_ptr<ViewResource> viewResource(ViewResource::create(view,
898                                                                       m_locale,
899                                                                       client,
900                                                                       getConnection(),
901                                                                       ID,
902                                                                       agentPath));
903     client->attach(boost::shared_ptr<Resource>(viewResource));
904
905     // created local resource
906     result->done(viewResource->getPath());
907 }
908
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)
913 {
914     try {
915         boost::shared_ptr<Session> session = m_server->startInternalSession(config,
916                                                                             flags,
917                                                                             boost::bind(&Manager::doSession,
918                                                                                         this,
919                                                                                         _1,
920                                                                                         result,
921                                                                                         callback));
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));
925         }
926     } catch (...) {
927         // Tell caller about specific error.
928         dbusErrorCallback(result);
929     }
930 }
931
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)
935 {
936     try {
937         boost::shared_ptr<Session> session = weakSession.lock();
938         if (!session) {
939             // Destroyed already?
940             return;
941         }
942         // Drop permanent reference, session will be destroyed when we
943         // return.
944         m_pending.remove(std::make_pair(result, session));
945
946         // Now run the operation.
947         callback(session);
948     } catch (...) {
949         // Tell caller about specific error.
950         dbusErrorCallback(result);
951     }
952 }
953
954 void Manager::getActiveAddressBooks(std::vector<std::string> &dbIDs)
955 {
956     BOOST_FOREACH (const std::string &uuid, m_enabledEBooks) {
957         if (uuid == "system-address-book") {
958             dbIDs.push_back("");
959         } else {
960             dbIDs.push_back(DB_PEER_PREFIX + uuid.substr(strlen(MANAGER_PREFIX)));
961         }
962     }
963 }
964
965 void Manager::setActiveAddressBooks(const std::vector<std::string> &dbIDs)
966 {
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");
979         } else {
980             SE_THROW("invalid address book ID: " + dbID);
981         }
982     }
983
984     // Swap and set in aggregator.
985     std::swap(uuids, m_enabledEBooks);
986     initDatabases();
987     m_configNode->writeProperty(MANAGER_CONFIG_ACTIVE_ADDRESS_BOOKS_PROPERTY,
988                                 InitStateString(boost::join(m_enabledEBooks, " "), true));
989     m_configNode->flush();
990 }
991
992 void Manager::initDatabases()
993 {
994     m_folks->setDatabases(m_enabledEBooks);
995 }
996
997 static void checkPeerUID(const std::string &uid)
998 {
999     const pcrecpp::RE re("[-a-z0-9]*");
1000     if (!re.FullMatch(uid)) {
1001         SE_THROW(StringPrintf("invalid peer uid: %s", uid.c_str()));
1002     }
1003 }
1004
1005 void Manager::createPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1006                          const std::string &uid, const StringMap &properties)
1007 {
1008     setPeer(result, uid, properties, CREATE_PEER);
1009 }
1010
1011 void Manager::modifyPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1012                          const std::string &uid, const StringMap &properties)
1013 {
1014     setPeer(result, uid, properties, SET_PEER);
1015 }
1016
1017 void Manager::setPeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1018                       const std::string &uid, const StringMap &properties,
1019                       ConfigureMode mode)
1020 {
1021     checkPeerUID(uid);
1022     runInSession(StringPrintf("@%s%s",
1023                               MANAGER_PREFIX,
1024                               uid.c_str()),
1025                  Server::SESSION_FLAG_NO_SYNC,
1026                  result,
1027                  boost::bind(&Manager::doSetPeer, this, _1, result, uid, properties, mode));
1028 }
1029
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";
1042
1043 static std::string GetEssential(const StringMap &properties, const char *key,
1044                                 bool allowEmpty = false)
1045 {
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",
1050                               key,
1051                               allowEmpty ? "" : " to a non-empty value"));
1052     }
1053     return entry;
1054 }
1055
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,
1059                         ConfigureMode mode)
1060 {
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()));
1074         }
1075         maxLogDirs = boost::lexical_cast<unsigned>(maxsessions);
1076     }
1077
1078     std::string localDatabaseName = MANAGER_PREFIX + uid;
1079     std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str());
1080
1081
1082     SE_LOG_DEBUG(NULL, "%s: creating config for protocol %s",
1083                  uid.c_str(),
1084                  protocol.c_str());
1085
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()));
1091         }
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()));
1097         }
1098     }
1099
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));
1104         switch (mode) {
1105         case CREATE_PEER:
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())));
1109                 return;
1110             }
1111             break;
1112         case MODIFY_PEER:
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())));
1116                 return;
1117             }
1118             break;
1119         case SET_PEER:
1120             // May or may not exist, doesn't matter.
1121             break;
1122         }
1123
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);
1133         }
1134         if (!maxsessions.empty()) {
1135             config->setMaxLogDirs(maxLogDirs);
1136         }
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);
1143         config->flush();
1144         // Ensure that database exists.
1145         SyncSourceParams params(MANAGER_LOCAL_SOURCE,
1146                                 config->getSyncSourceNodes(MANAGER_LOCAL_SOURCE),
1147                                 config,
1148                                 context);
1149         boost::scoped_ptr<SyncSource> syncSource(SyncSource::createSource(params));
1150         SyncSource::Databases databases = syncSource->getDatabases();
1151         bool found = false;
1152         BOOST_FOREACH (const SyncSource::Database &database, databases) {
1153             if (database.m_uri == localDatabaseName) {
1154                 found = true;
1155                 break;
1156             }
1157         }
1158         if (!found) {
1159             syncSource->createDatabase(SyncSource::Database(localDatabaseName, localDatabaseName));
1160         }
1161
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);
1171         }
1172         config->setLogLevel(atoi(getEnv("SYNCEVOLUTION_LOGLEVEL", "0")));
1173         if (!maxsessions.empty()) {
1174             config->setMaxLogDirs(maxLogDirs);
1175         }
1176         source = config->getSyncSourceConfig(MANAGER_REMOTE_SOURCE);
1177         if (protocol == PEER_PBAP_PROTOCOL) {
1178             // PBAP
1179             source->setDatabaseID("obex-bt://" + address);
1180             source->setBackend("pbap");
1181         } else {
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");
1187         }
1188         config->flush();
1189     } else {
1190         SE_THROW(StringPrintf("peer config: %s=%s not supported",
1191                               PEER_KEY_PROTOCOL,
1192                               protocol.c_str()));
1193     }
1194
1195     // Report success.
1196     SE_LOG_DEBUG(NULL, "%s: created config for protocol %s",
1197                  uid.c_str(),
1198                  protocol.c_str());
1199     result->done();
1200 }
1201
1202 Manager::PeersMap Manager::getAllPeers()
1203 {
1204     PeersMap peers;
1205
1206     SyncConfig::ConfigList configs = SyncConfig::getConfigs();
1207     std::string prefix = StringPrintf("%s@%s",
1208                                       MANAGER_LOCAL_CONFIG,
1209                                       MANAGER_PREFIX);
1210
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];
1222             }
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,
1227                                                      MANAGER_PREFIX,
1228                                                      uid.c_str()));
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://"));
1236                     }
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://"));
1241                     }
1242                 }
1243             }
1244         }
1245     }
1246
1247     return peers;
1248 }
1249
1250
1251 void Manager::removePeer(const boost::shared_ptr<GDBusCXX::Result0> &result,
1252                          const std::string &uid)
1253 {
1254     checkPeerUID(uid);
1255     runInSession(StringPrintf("@%s%s",
1256                               MANAGER_PREFIX,
1257                               uid.c_str()),
1258                  Server::SESSION_FLAG_NO_SYNC,
1259                  result,
1260                  boost::bind(&Manager::doRemovePeer, this, _1, result, uid));
1261 }
1262
1263 void Manager::doRemovePeer(const boost::shared_ptr<Session> &session,
1264                            const boost::shared_ptr<GDBusCXX::Result0> &result,
1265                            const std::string &uid)
1266 {
1267     std::string localDatabaseName = MANAGER_PREFIX + uid;
1268     std::string context = StringPrintf("@%s%s", MANAGER_PREFIX, uid.c_str());
1269
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);
1274     initDatabases();
1275     m_configNode->writeProperty(MANAGER_CONFIG_SORT_PROPERTY,
1276                                 InitStateString(boost::join(m_enabledEBooks, " "), true));
1277     m_configNode->flush();
1278
1279     // Access config via context (includes sync and target config).
1280     boost::shared_ptr<SyncConfig> config(new SyncConfig(context));
1281
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,
1288                                     nodes,
1289                                     config,
1290                                     context);
1291             boost::scoped_ptr<SyncSource> syncSource(SyncSource::createSource(params));
1292             SyncSource::Databases databases = syncSource->getDatabases();
1293             bool found = false;
1294             BOOST_FOREACH (const SyncSource::Database &database, databases) {
1295                 if (database.m_uri == localDatabaseName) {
1296                     found = true;
1297                     break;
1298                 }
1299             }
1300             if (found) {
1301                 syncSource->deleteDatabase(localDatabaseName, SyncSource::REMOVE_DATA_FORCE);
1302             }
1303         }
1304     }
1305
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));
1312     config->remove();
1313     config->flush();
1314
1315     // Report success.
1316     result->done();
1317 }
1318
1319 void Manager::syncPeer(const boost::shared_ptr<GDBusCXX::Result1<SyncResult> > &result,
1320                        const std::string &uid)
1321 {
1322     checkPeerUID(uid);
1323     runInSession(StringPrintf("%s@%s%s",
1324                               MANAGER_LOCAL_CONFIG,
1325                               MANAGER_PREFIX,
1326                               uid.c_str()),
1327                  Server::SESSION_FLAG_NO_SYNC,
1328                  result,
1329                  boost::bind(&Manager::doSyncPeer, this, _1, result, uid));
1330 }
1331
1332 static Manager::SyncResult SyncReport2Result(const SyncReport &report)
1333 {
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);
1341     }
1342     result["modified"] = added || updated || removed;
1343     result["added"] = added;
1344     result["updated"] = updated;
1345     result["removed"] = removed;
1346     return result;
1347 }
1348
1349 static void doneSyncPeer(const boost::shared_ptr<GDBusCXX::Result1<Manager::SyncResult> > &result,
1350                          SyncMLStatus status,
1351                          const SyncReport &report)
1352 {
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()"));
1358     } else {
1359         result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_BAD_STATUS, Status2String(status)));
1360     }
1361 }
1362
1363 void Manager::doSyncPeer(const boost::shared_ptr<Session> &session,
1364                          const boost::shared_ptr<GDBusCXX::Result1<SyncResult> > &result,
1365                          const std::string &uid)
1366 {
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));
1371
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") {
1380         syncMode = "pbap";
1381     }
1382
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
1385     // doSession().
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));
1389 }
1390
1391 void Manager::report2SyncProgress(const std::string &uid,
1392                                   const std::string &sourceName,
1393                                   const SyncSourceReport &source)
1394 {
1395     SyncReport report;
1396     report.addSyncSourceReport("foo", source);
1397     emitSyncProgress(uid, "modified", SyncReport2Result(report));
1398 }
1399
1400 void Manager::stopSync(const boost::shared_ptr<GDBusCXX::Result0> &result,
1401                        const std::string &uid)
1402 {
1403     checkPeerUID(uid);
1404
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,
1409                                               MANAGER_PREFIX,
1410                                               uid.c_str());
1411
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);
1420         }
1421     }
1422
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;
1428     if (session) {
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)));
1434             aborting = true;
1435         }
1436     }
1437     if (!aborting) {
1438         result->done();
1439     }
1440 }
1441
1442 void Manager::addContact(const boost::shared_ptr< GDBusCXX::Result1<std::string> > &result,
1443                          const std::string &addressbook,
1444                          const PersonaDetails &details)
1445 {
1446     try {
1447         if (!addressbook.empty()) {
1448             SE_THROW("only the system address book is writable");
1449         }
1450         m_folks->addContact(createDBusCb(result), details);
1451     } catch (...) {
1452         dbusErrorCallback(result);
1453     }
1454 }
1455
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)
1460 {
1461     try {
1462         if (!addressbook.empty()) {
1463             SE_THROW("only the system address book is writable");
1464         }
1465         m_folks->modifyContact(createDBusCb(result),
1466                                localID,
1467                                details);
1468     } catch (...) {
1469         dbusErrorCallback(result);
1470     }
1471 }
1472
1473 void Manager::removeContact(const boost::shared_ptr<GDBusCXX::Result0> &result,
1474                             const std::string &addressbook,
1475                             const std::string &localID)
1476 {
1477     try {
1478         if (!addressbook.empty()) {
1479             SE_THROW("only the system address book is writable");
1480         }
1481         m_folks->removeContact(createDBusCb(result),
1482                                localID);
1483     } catch (...) {
1484         dbusErrorCallback(result);
1485     }
1486 }
1487
1488 SE_END_CXX