Imported Upstream version 1.4.99.2
[platform/upstream/syncevolution.git] / src / syncevo / SyncSource.cpp
1 /*
2  * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #ifndef _GNU_SOURCE
22 # define _GNU_SOURCE 1
23 #endif
24 #include <dlfcn.h>
25
26 #include <syncevo/SyncSource.h>
27 #include <syncevo/SyncContext.h>
28 #include <syncevo/util.h>
29
30 #include <syncevo/SynthesisEngine.h>
31 #include <synthesis/SDK_util.h>
32 #include <synthesis/sync_dbapidef.h>
33
34 #include <boost/bind.hpp>
35 #include <boost/algorithm/string/join.hpp>
36 #include <boost/algorithm/string/split.hpp>
37 #include <boost/algorithm/string/classification.hpp>
38 #include <boost/algorithm/string/predicate.hpp>
39 #include <boost/lambda/lambda.hpp>
40
41 #include <ctype.h>
42 #include <errno.h>
43 #include <unistd.h>
44
45 #include <fstream>
46 #include <iostream>
47
48 #ifdef ENABLE_UNIT_TESTS
49 #include "test.h"
50 #endif
51
52 #include <syncevo/declarations.h>
53 SE_BEGIN_CXX
54
55 void SyncSourceBase::throwError(const SourceLocation &where, const string &action, int error)
56 {
57     std::string what = action + ": " + strerror(error);
58     // be as specific if we can be: relevant for the file backend,
59     // which is expected to return STATUS_NOT_FOUND == 404 for "file
60     // not found"
61     if (error == ENOENT) {
62         throwError(where, STATUS_NOT_FOUND, what);
63     } else {
64         throwError(where, what);
65     }
66 }
67
68 void SyncSourceBase::throwError(const SourceLocation &where, const string &failure)
69 {
70     Exception::throwError(where, string(getDisplayName()) + ": " + failure);
71 }
72
73 void SyncSourceBase::throwError(const SourceLocation &where, SyncMLStatus status, const string &failure)
74 {
75     Exception::throwError(where, status, getDisplayName() + ": " + failure);
76 }
77
78 SyncMLStatus SyncSourceBase::handleException(HandleExceptionFlags flags)
79 {
80     SyncMLStatus res = Exception::handle(getDisplayName(), flags);
81     return res == STATUS_FATAL ?
82         STATUS_DATASTORE_FAILURE :
83         res;
84 }
85
86 void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
87 {
88     stringstream xmlstream;
89     SynthesisInfo info;
90
91     getSynthesisInfo(info, fragments);
92
93     xmlstream <<
94         "      <plugin_module>SyncEvolution</plugin_module>\n";
95     if (info.m_earlyStartDataRead) {
96         xmlstream <<
97             "      <plugin_earlystartdataread>yes</plugin_earlystartdataread>\n";
98     }
99     if (info.m_readOnly) {
100          xmlstream <<
101              "      <!-- if this is set to 'yes', SyncML clients can only read\n"
102              "           from the database, but make no modifications -->\n"
103              "      <readonly>yes</readonly>\n";
104     }
105     xmlstream <<
106         "      <plugin_datastoreadmin>" <<
107         (serverModeEnabled() ? "yes" : "no") <<
108         "</plugin_datastoreadmin>\n"
109         "      <fromremoteonlysupport> yes </fromremoteonlysupport>\n"
110         "      <canrestart>yes</canrestart>\n";
111     if (info.m_globalIDs) {
112         xmlstream << 
113             "      <syncmode>1122583000</syncmode>";
114     }
115     xmlstream <<
116         "\n"
117         "      <!-- conflict strategy: Newer item wins\n"
118         "           You can set 'server-wins' or 'client-wins' as well\n"
119         "           if you want to give one side precedence\n"
120         "      -->\n"
121         "      <conflictstrategy>newer-wins</conflictstrategy>\n"
122         "\n"
123         "      <!-- on slowsync: do not duplicate items even if not fully equal\n"
124         "           You can set this to 'duplicate' to avoid possible data loss\n"
125         "           resulting from merging\n"
126         "      -->\n"
127         "      <slowsyncstrategy>newer-wins</slowsyncstrategy>\n"
128         "\n"
129         "      <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
130         "      <datacharset>UTF-8</datacharset>\n"
131         "      <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
132         "      <datalineends>unix</datalineends>\n"
133         "\n"
134         "      <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
135         "           rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
136         "      <datatimezone>SYSTEM</datatimezone>\n"
137         "\n"
138         "      <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
139         "           we must make sure this identifier is stored (and not only the sync time) -->\n"
140         "      <storesyncidentifiers>yes</storesyncidentifiers>\n"
141         "\n";
142     
143     xmlstream <<
144         "      <!-- Mapping of the fields to the fieldlist -->\n"
145         "      <fieldmap fieldlist='" << info.m_fieldlist << "'>\n";
146     if (!info.m_profile.empty()) {
147         xmlstream <<
148             "        <initscript><![CDATA[\n"
149             "           string itemdata;\n"
150             "        ]]></initscript>\n"
151             "        <beforewritescript><![CDATA[\n";
152         if(!info.m_beforeWriteScript.empty()) {
153             xmlstream << 
154                 "           " << info.m_beforeWriteScript << "\n";
155         }
156         xmlstream <<
157             "           itemdata = MAKETEXTWITHPROFILE(" << info.m_profile << ", \"" << info.m_backendRule << "\");\n"
158             "        ]]></beforewritescript>\n"
159             "        <afterreadscript><![CDATA[\n"
160             "           PARSETEXTWITHPROFILE(itemdata, " << info.m_profile << ", \"" << info.m_backendRule << "\");\n";
161         if(!info.m_afterReadScript.empty()) {
162             xmlstream << 
163                 "           " << info.m_afterReadScript<< "\n";
164         }
165         xmlstream <<
166             "        ]]></afterreadscript>\n"
167             "        <map name='data' references='itemdata' type='string'/>\n";
168     }
169     xmlstream << 
170         "        <automap/>\n"
171         "      </fieldmap>\n"
172         "\n";
173
174     xmlstream <<
175         "      <!-- datatypes supported by this datastore -->\n"
176         "      <typesupport>\n" <<
177         info.m_datatypes <<
178         "      </typesupport>\n";
179
180     // arbitrary configuration options, can override the ones above
181     xmlstream << info.m_datastoreOptions;
182
183     xml = xmlstream.str();
184 }
185
186 string SyncSourceBase::getNativeDatatypeName()
187 {
188     SynthesisInfo info;
189     XMLConfigFragments fragments;
190     getSynthesisInfo(info, fragments);
191     return info.m_native;
192 }
193
194 SyncSourceBase::Operations::Operations(SyncSourceName &source) :
195     m_startDataRead(source),
196     m_endDataRead(source),
197     m_startDataWrite(source),
198     m_finalizeLocalID(source),
199     m_endDataWrite(source),
200     m_readNextItem(source),
201     m_readItemAsKey(source),
202     m_insertItemAsKey(source),
203     m_updateItemAsKey(source),
204     m_deleteItem(source),
205     m_loadAdminData(source),
206     m_saveAdminData(source),
207     m_insertMapItem(source),
208     m_updateMapItem(source),
209     m_deleteMapItem(source),
210     m_deleteBlob(source)
211 {
212 }
213
214 static void BumpCounter(int32_t &counter)
215 {
216     counter++;
217 }
218
219 SyncSource::SyncSource(const SyncSourceParams &params) :
220     SyncSourceConfig(params.m_name, params.m_nodes),
221     m_operations(*this),
222     m_numDeleted(0),
223     m_added(0),
224     m_updated(0),
225     m_deleted(0),
226     m_forceSlowSync(false),
227     m_database("", ""),
228     m_name(params.getDisplayName()),
229     m_needChanges(true)
230 {
231     m_operations.m_insertItemAsKey.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_added)));
232     m_operations.m_updateItemAsKey.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_updated)));
233     m_operations.m_deleteItem.getPreSignal().connect(boost::bind(BumpCounter, boost::ref(m_deleted)));
234 }
235
236 SDKInterface *SyncSource::getSynthesisAPI() const
237 {
238     return m_synthesisAPI.empty() ?
239         NULL :
240         static_cast<SDKInterface *>(m_synthesisAPI[m_synthesisAPI.size() - 1]);
241 }
242
243 void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI)
244 {
245     m_synthesisAPI.push_back(synthesisAPI);
246 }
247
248 void SyncSource::popSynthesisAPI() {
249     m_synthesisAPI.pop_back();
250 }
251
252 SourceRegistry &SyncSource::getSourceRegistry()
253 {
254     static SourceRegistry sourceRegistry;
255     return sourceRegistry;
256 }
257
258 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
259                                        bool enabled,
260                                        Create_t create,
261                                        const string &typeDescr,
262                                        const Values &typeValues) :
263     m_shortDescr(shortDescr),
264     m_enabled(enabled),
265     m_create(create),
266     m_typeDescr(typeDescr),
267     m_typeValues(typeValues)
268 {
269     SourceRegistry &registry(SyncSource::getSourceRegistry());
270
271     // insert sorted by description to have deterministic ordering
272     for(SourceRegistry::iterator it = registry.begin();
273         it != registry.end();
274         ++it) {
275         if ((*it)->m_shortDescr > shortDescr) {
276             registry.insert(it, this);
277             return;
278         }
279     }
280     registry.push_back(this);
281 }
282
283 class InactiveSyncSource : public SyncSource
284 {
285 public:
286     InactiveSyncSource(const SyncSourceParams &params) : SyncSource(params) {}
287
288     virtual bool isInactive() const { return true; }
289     virtual void enableServerMode() {}
290     virtual bool serverModeEnabled() const { return false; }
291     virtual void getSynthesisInfo(SyncEvo::SyncSourceBase::SynthesisInfo&, SyncEvo::XMLConfigFragments&) { throwError(SE_HERE, "inactive"); }
292     virtual Databases getDatabases() { throwError(SE_HERE, "inactive"); return Databases(); }
293     virtual void open() { throwError(SE_HERE, "inactive"); }
294     virtual void close() { throwError(SE_HERE, "inactive"); }
295     virtual std::string getPeerMimeType() const { return ""; }
296 };
297
298 SyncSource *RegisterSyncSource::InactiveSource(const SyncSourceParams &params)
299 {
300     return new InactiveSyncSource(params);
301 }
302
303 TestRegistry &SyncSource::getTestRegistry()
304 {
305     static TestRegistry testRegistry;
306     return testRegistry;
307 }
308
309 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
310     m_configName(configName),
311     m_testCaseName(testCaseName)
312 {
313     SyncSource::getTestRegistry().push_back(this);
314 }
315
316 static class ScannedModules {
317 public:
318     ScannedModules() {
319 #ifdef ENABLE_MODULES
320         list<pair <string, boost::shared_ptr<ReadDir> > > dirs;
321         /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search
322         backends from this path instead. */
323         string backend_dir (SYNCEVO_BACKEND);
324         char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR");
325         if (backend_env && strlen(backend_env)){
326             backend_dir = backend_env;
327         }
328         boost::shared_ptr<ReadDir> dir (new ReadDir (backend_dir, false));
329         string dirpath (backend_dir);
330         // Base name (= no dir, no .so suffix) mapping to full file
331         // name (including .so).
332         std::map<std::string, std::string> candidates;
333         // scan directories for matching module names
334         do {
335             debug<<"Scanning backend libraries in " <<dirpath <<endl;
336             BOOST_FOREACH (const string &entry, *dir) {
337                 if (isDir (dirpath + '/' + entry)) {
338                     /* This is a 2-level dir, this corresponds to loading
339                      * backends from current building directory. The library
340                      * should reside in .libs sub directory.*/
341                     string path = dirpath + '/' + entry +"/.libs";
342                     if (isDir (path)) {
343                         boost::shared_ptr<ReadDir> subdir (new ReadDir (path, false));
344                         dirs.push_back (make_pair(path, subdir));
345                     }
346                     continue;
347                 }
348                 if (boost::ends_with(entry, ".so")) {
349                     string fullpath = dirpath + '/' + entry;
350                     fullpath = normalizePath(fullpath);
351                     candidates[entry.substr(0, entry.size() - 3)] = fullpath;
352                 }
353             }
354             if (!dirs.empty()){
355                 dirpath = dirs.front().first;
356                 dir = dirs.front().second;
357                 dirs.pop_front();
358             } else {
359                 break;
360             }
361         } while (true);
362
363         // Look at foo-<version> before foo. If there is more than
364         // one version and the version sorts lexically, the "highest"
365         // one will be checked first, too.
366         //
367         // The intention is to try loading syncebook-2 (with explicit
368         // library dependencies) first, then skip syncebook if loading
369         // of syncebook-2 succeeded. If loading of syncebook-2 fails
370         // due to missing libraries, we proceed to use syncebook.
371         BOOST_REVERSE_FOREACH (const StringPair &entry, candidates) {
372             const std::string &basename = entry.first;
373             const std::string &fullpath = entry.second;
374             std::string replacement;
375             std::string modname;
376             size_t offset = basename.rfind('-');
377             if (offset != basename.npos) {
378                 modname = basename.substr(0, offset);
379             } else {
380                 modname = basename;
381             }
382             BOOST_FOREACH (const std::string &l, m_available) {
383                 if (boost::starts_with(l, modname)) {
384                     replacement = l;
385                     break;
386                 }
387             }
388             if (!replacement.empty()) {
389                 debug << "Skipping " << basename << " = " << fullpath << " because a more recent version of it was already loaded: " << replacement;
390                 continue;
391             }
392
393             // Open the shared object so that backend can register
394             // itself. We keep that pointer, so never close the
395             // module!
396             // RTLD_LAZY is needed for the WebDAV backend, which
397             // needs to do an explicit dlopen() of libneon in compatibility
398             // mode before any of the neon functions can be resolved.
399             void *dlhandle = dlopen(fullpath.c_str(), RTLD_LAZY|RTLD_GLOBAL);
400             // remember which modules were found and which were not
401             if (dlhandle) {
402                 debug<<"Loading backend library "<<basename<<endl;
403                 info<<"Loading backend library "<<fullpath<<endl;
404                 m_available.push_back(basename);
405             } else {
406                 debug<<"Loading backend library "<<basename<<"failed "<< dlerror()<<endl;
407             }
408         }
409 #endif
410     }
411     list<string> m_available;
412     std::ostringstream debug, info;
413 } scannedModules;
414
415 string SyncSource::backendsInfo() {
416     return scannedModules.info.str();
417 }
418 string SyncSource::backendsDebug() {
419     return scannedModules.debug.str();
420 }
421
422 void SyncSource::requestAnotherSync()
423 {
424     // At the moment the per-source request to restart cannot be
425     // stored; instead only a per-session request is set. That's okay
426     // for now because restarting is limited to sessions with only
427     // one source active (intentional simplification).
428     SE_LOG_DEBUG(getDisplayName(), "requesting another sync");
429     SyncContext::requestAnotherSync();
430 }
431
432
433 SyncSource *SyncSource::createSource(const SyncSourceParams &params, bool error, SyncConfig *config)
434 {
435     SourceType sourceType = getSourceType(params.m_nodes);
436
437     if (sourceType.m_backend == "virtual") {
438         SyncSource *source = NULL;
439         source = new VirtualSyncSource(params, config);
440         if (error && !source) {
441             Exception::throwError(SE_HERE, params.getDisplayName() + ": virtual source cannot be instantiated");
442         }
443         return source;
444     }
445
446     const SourceRegistry &registry(getSourceRegistry());
447     auto_ptr<SyncSource> source;
448     BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
449         auto_ptr<SyncSource> nextSource(sourceInfos->m_create(params));
450         if (nextSource.get()) {
451             if (source.get()) {
452                 Exception::throwError(SE_HERE, params.getDisplayName() + ": backend " + sourceType.m_backend +
453                                         " is ambiguous, avoid the alias and pick a specific backend instead directly");
454             }
455             source = nextSource;
456         }
457     }
458     if (source.get()) {
459         return source.release();
460     }
461
462     if (error) {
463         string backends;
464         if (!scannedModules.m_available.empty()) {
465             backends += "by any of the backend modules (";
466             backends += boost::join(scannedModules.m_available, ", ");
467             backends += ") ";
468         }
469         string problem =
470             StringPrintf("%s%sbackend not supported %sor not correctly configured (backend=%s databaseFormat=%s syncFormat=%s)",
471                          params.m_name.c_str(),
472                          params.m_name.empty() ? "" : ": ",
473                          backends.c_str(),
474                          sourceType.m_backend.c_str(),
475                          sourceType.m_localFormat.c_str(),
476                          sourceType.m_format.c_str());
477         Exception::throwError(SE_HERE, SyncMLStatus(sysync::LOCERR_CFGPARSE), problem);
478     }
479
480     return NULL;
481 }
482
483 SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error,
484                                             const char *prefix)
485 {
486     std::string config = "target-config@client-test";
487     const char *server = getenv("CLIENT_TEST_SERVER");
488     if (server) {
489         config += "-";
490         config += server;
491     }
492     boost::shared_ptr<SyncConfig> context(new SyncConfig(config));
493     SyncSourceNodes nodes = context->getSyncSourceNodes(name);
494     SyncSourceParams params(name, nodes, context);
495     PersistentSyncSourceConfig sourceconfig(name, nodes);
496     sourceconfig.setSourceType(type);
497     if (prefix) {
498         sourceconfig.setDatabaseID(string(prefix) + name + "_1");
499     }
500     return createSource(params, error);
501 }
502
503 VirtualSyncSource::VirtualSyncSource(const SyncSourceParams &params, SyncConfig *config) :
504     DummySyncSource(params)
505 {
506     if (config) {
507         std::string evoSyncSource = getDatabaseID();
508         BOOST_FOREACH(std::string name, getMappedSources()) {
509             if (name.empty()) {
510                 throwError(SE_HERE, StringPrintf("configuration of underlying sources contains empty source name: database = '%s'",
511                                                  evoSyncSource.c_str()));
512             }
513             SyncSourceNodes source = config->getSyncSourceNodes(name);
514             SyncSourceParams params(name, source, boost::shared_ptr<SyncConfig>(config, SyncConfigNOP()));
515             boost::shared_ptr<SyncSource> syncSource(createSource(params, true, config));
516             m_sources.push_back(syncSource);
517         }
518         if (m_sources.size() != 2) {
519             throwError(SE_HERE, StringPrintf("configuration of underlying sources must contain exactly one calendar and one todo source (like calendar+todo): database = '%s'",
520                                              evoSyncSource.c_str()));
521         }
522     }
523 }
524
525 void VirtualSyncSource::open()
526 {
527     getDataTypeSupport();
528     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
529         source->open();
530     }
531 }
532
533 void VirtualSyncSource::close()
534 {
535     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
536         source->close();
537     }
538 }
539
540 std::vector<std::string> VirtualSyncSource::getMappedSources()
541 {
542     std::string evoSyncSource = getDatabaseID();
543     std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
544     return mappedSources;
545 }
546
547 std::string VirtualSyncSource::getDataTypeSupport()
548 {
549     string datatypes;
550     SourceType sourceType = getSourceType();
551     string type = sourceType.m_format;
552
553     datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
554     return datatypes;
555 }
556
557 SyncSource::Databases VirtualSyncSource::getDatabases()
558 {
559     SyncSource::Databases dbs;
560     BOOST_FOREACH (boost::shared_ptr<SyncSource> &source, m_sources) {
561         SyncSource::Databases sub = source->getDatabases();
562         if (sub.empty()) {
563             return dbs;
564         }
565     }
566     Database db ("calendar+todo", "");
567     dbs.push_back (db);
568     return dbs;
569 }
570
571 void SyncSourceSession::init(SyncSource::Operations &ops)
572 {
573     ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2);
574     ops.m_endDataRead = boost::lambda::constant(sysync::LOCERR_OK);
575     ops.m_startDataWrite = boost::lambda::constant(sysync::LOCERR_OK);
576     ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2);
577 }
578
579 sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken)
580 {
581     try {
582         beginSync(lastToken ? lastToken : "",
583                   resumeToken ? resumeToken : "");
584     } catch (const StatusException &ex) {
585         SyncMLStatus status = ex.syncMLStatus();
586         if (status == STATUS_SLOW_SYNC_508) {
587             // Not an error. Return it normally, without ERROR logging
588             // in our caller.
589             return status;
590         } else {
591             throw;
592         }
593     }
594     return sysync::LOCERR_OK;
595 }
596
597 sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken)
598 {
599     std::string token = endSync(success);
600     *newToken = StrAlloc(token.c_str());
601     return sysync::LOCERR_OK;
602 }
603
604 void SyncSourceChanges::init(SyncSource::Operations &ops)
605 {
606     ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3);
607 }
608
609 SyncSourceChanges::SyncSourceChanges() :
610     m_first(true)
611 {
612 }
613
614 bool SyncSourceChanges::addItem(const string &luid, State state)
615 {
616     pair<Items_t::iterator, bool> res = m_items[state].insert(luid);
617     return res.second;
618 }
619
620 bool SyncSourceChanges::reset()
621 {
622     bool removed = false;
623     for (int i = 0; i < MAX; i++) {
624         if (!m_items[i].empty()) {
625             m_items[i].clear();
626             removed = true;
627         }
628     }
629     m_first = true;
630     return removed;
631 }
632
633 sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID,
634                                             sysync::sInt32 *aStatus,
635                                             bool aFirst)
636 {
637     aID->item = NULL;
638     aID->parent = NULL;
639
640     if (m_first || aFirst) {
641         m_it = m_items[ANY].begin();
642         m_first = false;
643     }
644
645     if (m_it == m_items[ANY].end()) {
646         *aStatus = sysync::ReadNextItem_EOF;
647     } else {
648         const string &luid = *m_it;
649
650         if (m_items[NEW].find(luid) != m_items[NEW].end() ||
651             m_items[UPDATED].find(luid) != m_items[UPDATED].end()) {
652             *aStatus = sysync::ReadNextItem_Changed;
653         } else {
654             *aStatus = sysync::ReadNextItem_Unchanged;
655         }
656         aID->item = StrAlloc(luid.c_str());
657         ++m_it;
658     }
659
660     return sysync::LOCERR_OK;
661 }
662
663 void SyncSourceDelete::init(SyncSource::Operations &ops)
664 {
665     ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1);
666 }
667
668 sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID)
669 {
670     deleteItem(aID->item);
671     incrementNumDeleted();
672     return sysync::LOCERR_OK;
673 }
674
675
676 void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info,
677                                            XMLConfigFragments &fragments)
678 {
679     string type = getMimeType();
680
681     // default remote rule (local-storage.xml): suppresses empty properties
682     info.m_backendRule = "LOCALSTORAGE";
683
684     // We store entire items locally and thus have to make sure that
685     // they are complete by having the engine merge incoming and local
686     // data.
687     info.m_datastoreOptions += "      <updateallfields>true</updateallfields>\n";
688
689     if (type == "text/x-vcard") {
690         info.m_native = "vCard21";
691         info.m_fieldlist = "contacts";
692         info.m_profile = "\"vCard\", 1";
693         info.m_datatypes =
694             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
695             "        <use datatype='vCard30' mode='rw'/>\n";
696     } else if (type == "text/vcard") {
697         info.m_native = "vCard30";
698         info.m_fieldlist = "contacts";
699         info.m_profile = "\"vCard\", 2";
700         info.m_datatypes =
701             "        <use datatype='vCard21' mode='rw'/>\n"
702             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
703         // If a backend overwrites the m_beforeWriteScript, then it must
704         // include $VCARD_OUTGOING_PHOTO_VALUE_SCRIPT in its own script,
705         // otherwise it will be sent invalid, empty PHOTO;TYPE=unknown;VALUE=binary:
706         // properties.
707         info.m_beforeWriteScript = "$VCARD_BEFOREWRITE_SCRIPT;\n";
708         // Likewise for reading. This is needed to ensure proper merging
709         // of contact data.
710         info.m_afterReadScript = "$VCARD_AFTERREAD_SCRIPT;\n";
711     } else if (type == "text/x-calendar" || type == "text/x-vcalendar") {
712         info.m_native = "vCalendar10";
713         info.m_fieldlist = "calendar";
714         info.m_profile = "\"vCalendar\", 1";
715         info.m_datatypes =
716             "        <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
717             "        <use datatype='iCalendar20' mode='rw'/>\n";
718         /**
719          * here are two default implementations. If user wants to reset it,
720          * just implement its own getSynthesisInfo. If user wants to use this default
721          * implementations and its new scripts, it is possible to append its implementations
722          * to info.m_afterReadScript and info.m_beforeWriteScript.
723          */
724         info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
725         info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n"
726             "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
727     } else if (type == "text/calendar" ||
728                boost::starts_with(type, "text/calendar+")) {
729         info.m_native = "iCalendar20";
730         info.m_fieldlist = "calendar";
731         info.m_profile = "\"vCalendar\", 2";
732         info.m_datatypes =
733             "        <use datatype='vCalendar10' mode='rw'/>\n"
734             "        <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
735         info.m_beforeWriteScript = "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
736     } else if (type == "text/plain") {
737         info.m_fieldlist = "Note";
738         info.m_profile = "\"Note\", 2";
739     } else {
740         throwError(SE_HERE, string("default MIME type not supported: ") + type);
741     }
742
743     SourceType sourceType = getSourceType();
744     if (!sourceType.m_format.empty()) {
745         type = sourceType.m_format;
746     }
747     info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
748 }
749
750 std::string SyncSourceBase::getDataTypeSupport(const std::string &type,
751                                                bool forceFormat)
752 {
753     std::string datatypes;
754
755     if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
756         datatypes =
757             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
758         if (!forceFormat) {
759             datatypes +=
760                 "        <use datatype='vCard30' mode='rw'/>\n";
761         }
762     } else if (type == "text/vcard:3.0" || type == "text/vcard") {
763         datatypes =
764             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
765         if (!forceFormat) {
766             datatypes +=
767                 "        <use datatype='vCard21' mode='rw'/>\n";
768         }
769     } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar"
770              || type == "text/x-calendar:1.0" || type == "text/x-calendar") {
771         datatypes =
772             "        <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
773         if (!forceFormat) {
774             datatypes +=
775                 "        <use datatype='icalendar20' mode='rw'/>\n";
776         }
777     } else if (type == "text/calendar:2.0" || type == "text/calendar") {
778         datatypes =
779             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
780         if (!forceFormat) {
781             datatypes +=
782                 "        <use datatype='vcalendar10' mode='rw'/>\n";
783         }
784     } else if (type == "text/calendar+plain") {
785         datatypes =
786             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n"
787             "        <use datatype='journaltext10' mode='rw'/>\n"
788             "        <use datatype='journaltext11' mode='rw'/>\n";
789     } else if (type == "text/plain:1.0" || type == "text/plain") {
790         // note10 are the same as note11, so ignore force format
791         datatypes =
792             "        <use datatype='note10' mode='rw' preferred='yes'/>\n"
793             "        <use datatype='note11' mode='rw'/>\n";
794     } else if (type.empty()) {
795         throwError(SE_HERE, "no MIME type configured");
796     } else {
797         throwError(SE_HERE, string("configured MIME type not supported: ") + type);
798     }
799
800     return datatypes;
801 }
802
803 sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
804 {
805     std::string item;
806
807     readItem(aID->item, item);
808     TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size());
809     return res;
810 }
811
812 SyncSource::Operations::InsertItemAsKeyResult_t SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
813 {
814     SharedBuffer data;
815     TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
816
817     if (!res) {
818         InsertItemResult inserted = insertItem("", data.get());
819         switch (inserted.m_state) {
820         case ITEM_OKAY:
821             break;
822         case ITEM_AGAIN:
823             // Skip setting the newID.
824             return Operations::InsertItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _2, inserted.m_continue));
825             break;
826         case ITEM_REPLACED:
827             res = sysync::DB_DataReplaced;
828             break;
829         case ITEM_MERGED:
830             res = sysync::DB_DataMerged;
831             break;
832         case ITEM_NEEDS_MERGE:
833             res = sysync::DB_Conflict;
834             break;
835         }
836         newID->item = StrAlloc(inserted.m_luid.c_str());
837     }
838
839     return res;
840 }
841
842 SyncSource::Operations::UpdateItemAsKeyResult_t SyncSourceSerialize::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
843 {
844     SharedBuffer data;
845     TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
846
847     if (!res) {
848         InsertItemResult inserted = insertItem(aID->item, data.get());
849         switch (inserted.m_state) {
850         case ITEM_OKAY:
851             break;
852         case ITEM_AGAIN:
853             // Skip setting the newID.
854             return Operations::UpdateItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _3, inserted.m_continue));
855             break;
856         case ITEM_REPLACED:
857             res = sysync::DB_DataReplaced;
858             break;
859         case ITEM_MERGED:
860             res = sysync::DB_DataMerged;
861             break;
862         case ITEM_NEEDS_MERGE:
863             res = sysync::DB_Conflict;
864             break;
865         }
866         newID->item = StrAlloc(inserted.m_luid.c_str());
867     }
868
869     return res;
870 }
871
872 sysync::TSyError SyncSourceSerialize::insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont)
873 {
874     // The engine cannot tell us when it needs results (for example,
875     // in the "final message received from peer" case in
876     // TSyncSession::EndMessage(), so assume that it does whenever it
877     // calls us again => flush and wait.
878     flushItemChanges();
879     finishItemChanges();
880
881     InsertItemResult inserted = cont();
882     TSyError res = sysync::LOCERR_OK;
883     switch (inserted.m_state) {
884     case ITEM_OKAY:
885         break;
886     case ITEM_AGAIN:
887         // Skip setting the newID.
888         return sysync::LOCERR_AGAIN;
889         break;
890     case ITEM_REPLACED:
891         res = sysync::DB_DataReplaced;
892         break;
893     case ITEM_MERGED:
894         res = sysync::DB_DataMerged;
895         break;
896     case ITEM_NEEDS_MERGE:
897         res = sysync::DB_Conflict;
898         break;
899     }
900     newID->item = StrAlloc(inserted.m_luid.c_str());
901     return res;
902 }
903
904 SyncSourceSerialize::InsertItemResult SyncSourceSerialize::insertItemRaw(const std::string &luid, const std::string &item)
905 {
906     InsertItemResult result = insertItem(luid, item);
907
908     while (result.m_state == ITEM_AGAIN) {
909         // Flush and wait, because caller (command line, restore) is
910         // not prepared to deal with asynchronous execution.
911         flushItemChanges();
912         finishItemChanges();
913         result = result.m_continue();
914     }
915
916     return result;
917 }
918
919 void SyncSourceSerialize::readItemRaw(const std::string &luid, std::string &item)
920 {
921     return readItem(luid, item);
922 }
923
924
925
926 void SyncSourceSerialize::init(SyncSource::Operations &ops)
927 {
928     ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey,
929                                       this, _1, _2);
930     ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
931                                         this, _1, _2);
932     ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::updateItemAsKey,
933                                         this, _1, _2, _3);
934 }
935
936
937 void ItemCache::init(const SyncSource::Operations::ConstBackupInfo &oldBackup,
938                      const SyncSource::Operations::BackupInfo &newBackup,
939                      bool legacy)
940 {
941     m_counter = 1;
942     m_legacy = legacy;
943     m_backup = newBackup;
944     m_hash2counter.clear();
945     m_dirname = oldBackup.m_dirname;
946     if (m_dirname.empty() || !oldBackup.m_node) {
947         return;
948     }
949
950     long numitems;
951     if (!oldBackup.m_node->getProperty("numitems", numitems)) {
952         return;
953     }
954     for (long counter = 1; counter <= numitems; counter++) {
955         stringstream key;
956         key << counter << m_hashSuffix;
957         Hash_t hash;
958         if (oldBackup.m_node->getProperty(key.str(), hash)) {
959             m_hash2counter[hash] = counter;
960         }
961     }
962 }
963
964 void ItemCache::reset()
965 {
966     // clean directory and start counting at 1 again
967     m_counter = 1;
968     rm_r(m_backup.m_dirname);
969     mkdir_p(m_backup.m_dirname);
970     m_backup.m_node->clear();
971 }
972
973 string ItemCache::getFilename(Hash_t hash)
974 {
975     Map_t::const_iterator it = m_hash2counter.find(hash);
976     if (it != m_hash2counter.end()) {
977         stringstream dirname;
978         dirname << m_dirname << "/" << it->second;
979         return dirname.str();
980     } else {
981         return "";
982     }
983 }
984
985 const char *ItemCache::m_hashSuffix =
986 #ifdef USE_SHA256
987     "-sha256"
988 #else
989     "-hash"
990 #endif
991 ;
992
993 void ItemCache::backupItem(const std::string &item,
994                            const std::string &uid,
995                            const std::string &rev)
996 {
997     stringstream filename;
998     filename << m_backup.m_dirname << "/" << m_counter;
999
1000     ItemCache::Hash_t hash = hashFunc(item);
1001     string oldfilename = getFilename(hash);
1002     if (!oldfilename.empty()) {
1003         // found old file with same content, reuse it via hardlink
1004         if (link(oldfilename.c_str(), filename.str().c_str())) {
1005             // Hard linking failed. Record this, then continue
1006             // by ignoring the old file.
1007             SE_LOG_DEBUG(NULL, "hard linking old %s new %s: %s",
1008                          oldfilename.c_str(),
1009                          filename.str().c_str(),
1010                          strerror(errno));
1011             oldfilename.clear();
1012         }
1013     }
1014
1015     if (oldfilename.empty()) {
1016         // write new file instead of reusing old one
1017         ofstream out(filename.str().c_str());
1018         out.write(item.c_str(), item.size());
1019         out.close();
1020         if (out.fail()) {
1021             SE_THROW(string("error writing ") + filename.str() + ": " + strerror(errno));
1022         }
1023     }
1024
1025     stringstream key;
1026     key << m_counter << "-uid";
1027     m_backup.m_node->setProperty(key.str(), uid);
1028     if (m_legacy) {
1029         // clear() does not remove the existing content, which was
1030         // intended here. This should have been key.str(""). As a
1031         // result, keys for -rev are longer than intended because they
1032         // start with the -uid part. We cannot change it now, because
1033         // that would break compatibility with nodes that use the
1034         // older, longer keys for -rev.
1035         // key.clear();
1036     } else {
1037         key.str("");
1038     }
1039     key << m_counter << "-rev";
1040     m_backup.m_node->setProperty(key.str(), rev);
1041     key.str("");
1042     key << m_counter << ItemCache::m_hashSuffix;
1043     m_backup.m_node->setProperty(key.str(), hash);
1044
1045     m_counter++;
1046 }
1047
1048 void ItemCache::finalize(BackupReport &report)
1049 {
1050     stringstream value;
1051     value << m_counter - 1;
1052     m_backup.m_node->setProperty("numitems", value.str());
1053     m_backup.m_node->flush();
1054
1055     report.setNumItems(m_counter - 1);
1056 }
1057
1058 void SyncSourceRevisions::initRevisions()
1059 {
1060     if (!m_revisionsSet) {
1061         // might still be filled with garbage from previous run
1062         m_revisions.clear();
1063         listAllItems(m_revisions);
1064         m_revisionsSet = true;
1065     }
1066 }
1067
1068
1069 void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1070                                      const SyncSource::Operations::BackupInfo &newBackup,
1071                                      BackupReport &report)
1072 {
1073     ItemCache cache;
1074     cache.init(oldBackup, newBackup, true);
1075
1076     bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
1077     RevisionMap_t buffer;
1078     RevisionMap_t *revisions;
1079     if (startOfSync) {
1080         initRevisions();
1081         revisions = &m_revisions;
1082     } else {
1083         listAllItems(buffer);
1084         revisions = &buffer;
1085     }
1086
1087     // Ensure that source knows what we are going to read.
1088     std::vector<std::string> uids;
1089     uids.reserve(revisions->size());
1090     BOOST_FOREACH(const StringPair &mapping, *revisions) {
1091         uids.push_back(mapping.first);
1092     }
1093
1094     // We may dump after a hint was already set when starting the
1095     // sync. Remember that and restore it when done. If we fail, we
1096     // don't need to restore, because then syncing will abort or skip
1097     // the source.
1098     ReadAheadOrder oldOrder;
1099     ReadAheadItems oldLUIDs;
1100     getReadAheadOrder(oldOrder, oldLUIDs);
1101
1102     setReadAheadOrder(READ_SELECTED_ITEMS, uids);
1103
1104     string item;
1105     errno = 0;
1106     BOOST_FOREACH(const StringPair &mapping, *revisions) {
1107         const string &uid = mapping.first;
1108         const string &rev = mapping.second;
1109         m_raw->readItemRaw(uid, item);
1110         cache.backupItem(item, uid, rev);
1111     }
1112
1113     setReadAheadOrder(oldOrder, oldLUIDs);
1114     cache.finalize(report);
1115 }
1116
1117 void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1118                                       bool dryrun,
1119                                       SyncSourceReport &report)
1120 {
1121     RevisionMap_t revisions;
1122     listAllItems(revisions);
1123
1124     long numitems;
1125     string strval;
1126     strval = oldBackup.m_node->readProperty("numitems");
1127     stringstream stream(strval);
1128     stream >> numitems;
1129
1130     for (long counter = 1; counter <= numitems; counter++) {
1131         stringstream key;
1132         key << counter << "-uid";
1133         string uid = oldBackup.m_node->readProperty(key.str());
1134         key.clear();
1135         key << counter << "-rev";
1136         string rev = oldBackup.m_node->readProperty(key.str());
1137         RevisionMap_t::iterator it = revisions.find(uid);
1138         report.incrementItemStat(report.ITEM_LOCAL,
1139                                  report.ITEM_ANY,
1140                                  report.ITEM_TOTAL);
1141         if (it != revisions.end() &&
1142             it->second == rev) {
1143             // item exists in backup and database with same revision:
1144             // nothing to do
1145         } else {
1146             // add or update, so need item
1147             stringstream filename;
1148             filename << oldBackup.m_dirname << "/" << counter;
1149             string data;
1150             if (!ReadFile(filename.str(), data)) {
1151                 throwError(SE_HERE, StringPrintf("restoring %s from %s failed: could not read file",
1152                                         uid.c_str(),
1153                                         filename.str().c_str()));
1154             }
1155             // TODO: it would be nicer to recreate the item
1156             // with the original revision. If multiple peers
1157             // synchronize against us, then some of them
1158             // might still be in sync with that revision. By
1159             // updating the revision here we force them to
1160             // needlessly receive an update.
1161             //
1162             // For the current peer for which we restore this is
1163             // avoided by the revision check above: unchanged
1164             // items aren't touched.
1165             SyncSourceReport::ItemState state =
1166                 it == revisions.end() ?
1167                 SyncSourceReport::ITEM_ADDED :   // not found in database, create anew
1168                 SyncSourceReport::ITEM_UPDATED;  // found, update existing item
1169             try {
1170                 report.incrementItemStat(report.ITEM_LOCAL,
1171                                          state,
1172                                          report.ITEM_TOTAL);
1173                 if (!dryrun) {
1174                     m_raw->insertItemRaw(it == revisions.end() ? "" : uid,
1175                                          data);
1176                 }
1177             } catch (...) {
1178                 report.incrementItemStat(report.ITEM_LOCAL,
1179                                          state,
1180                                          report.ITEM_REJECT);
1181                 throw;
1182             }
1183         }
1184
1185         // remove handled item from revision list so
1186         // that when we are done, the only remaining
1187         // items listed there are the ones which did
1188         // no exist in the backup
1189         if (it != revisions.end()) {
1190             revisions.erase(it);
1191         }
1192     }
1193
1194     // now remove items that were not in the backup
1195     BOOST_FOREACH(const StringPair &mapping, revisions) {
1196         try {
1197             report.incrementItemStat(report.ITEM_LOCAL,
1198                                      report.ITEM_REMOVED,
1199                                      report.ITEM_TOTAL);
1200             if (!dryrun) {
1201                 m_del->deleteItem(mapping.first);
1202             }
1203         } catch(...) {
1204             report.incrementItemStat(report.ITEM_LOCAL,
1205                                      report.ITEM_REMOVED,
1206                                      report.ITEM_REJECT);
1207             throw;
1208         }
1209     }
1210 }
1211
1212 bool SyncSourceRevisions::detectChanges(ConfigNode &trackingNode, ChangeMode mode)
1213 {
1214     bool forceSlowSync = false;
1215
1216     // erase content which might have been set in a previous call
1217     reset();
1218     if (!m_firstCycle) {
1219         // detectChanges() must have been called before;
1220         // don't trust our cached revisions in that case (not updated during sync!)
1221         // TODO: keep the revision map up-to-date as part of a sync and reuse it
1222         m_revisionsSet = false;
1223     } else {
1224         m_firstCycle = false;
1225     }
1226
1227     if (mode == CHANGES_NONE) {
1228         // shortcut because nothing changed: just copy our known item list
1229         ConfigProps props;
1230         trackingNode.readProperties(props);
1231
1232         RevisionMap_t revisions;
1233         BOOST_FOREACH(const StringPair &mapping, props) {
1234             const string &uid = mapping.first;
1235             const string &revision = mapping.second;
1236             addItem(uid);
1237             revisions[uid] = revision;
1238         }
1239         setAllItems(revisions);
1240         return false;
1241     }
1242
1243     if (!m_revisionsSet &&
1244         mode == CHANGES_FULL) {
1245         ConfigProps props;
1246         trackingNode.readProperties(props);
1247         if (!props.empty()) {
1248             // We were not asked to throw away all old information and
1249             // there is some that may be worth salvaging, so let's give
1250             // our derived class a chance to update it instead of having
1251             // to reread everything.
1252             //
1253             // The exact number of items at which the update method is
1254             // more efficient depends on the derived class; here we assume
1255             // that even a single item makes it worthwhile. The derived
1256             // class can always ignore the information if it has different
1257             // tradeoffs.
1258             //
1259             // TODO (?): an API which only provides the information
1260             // on demand...
1261             m_revisions.clear();
1262             m_revisions.insert(props.begin(), props.end());
1263             updateAllItems(m_revisions);
1264             // continue with m_revisions initialized below
1265             m_revisionsSet = true;
1266         }
1267     }
1268
1269     // traditional, slow fallback follows...
1270     initRevisions();
1271
1272     // Check whether we have valid revision information.  If not, then
1273     // we need to do a slow sync. The assumption here is that an empty
1274     // revision string marks missing information. When we don't need
1275     // change information, not having a revision string is okay.
1276     if (needChanges() &&
1277         !m_revisions.empty() &&
1278         m_revisions.begin()->second.empty()) {
1279         forceSlowSync = true;
1280         mode = CHANGES_SLOW;
1281     }
1282
1283     // If we don't need changes, then override the mode so that
1284     // we don't compute them below.
1285     if (!needChanges()) {
1286         mode = CHANGES_SLOW;
1287     }
1288
1289     // Delay setProperty calls until after checking all uids.
1290     // Necessary for MapSyncSource, which shares the revision among
1291     // several uids. Another advantage is that we can do the "find
1292     // deleted items" check with less entries (new items not added
1293     // yet).
1294     StringMap revUpdates;
1295
1296     if (mode == CHANGES_SLOW) {
1297         // Make tracking node identical to current set of items
1298         // by re-adding them below.
1299         trackingNode.clear();
1300     }
1301
1302     BOOST_FOREACH(const StringPair &mapping, m_revisions) {
1303         const string &uid = mapping.first;
1304         const string &revision = mapping.second;
1305
1306         // always remember the item, need full list
1307         addItem(uid);
1308
1309         // avoid unnecessary work in CHANGES_SLOW mode
1310         if (mode == CHANGES_SLOW) {
1311             trackingNode.setProperty(uid, revision);
1312         } else {
1313             // detect changes
1314             string serverRevision(trackingNode.readProperty(uid));
1315             if (!serverRevision.size()) {
1316                 addItem(uid, NEW);
1317                 revUpdates[uid] = revision;
1318             } else {
1319                 if (revision != serverRevision) {
1320                     addItem(uid, UPDATED);
1321                     revUpdates[uid] = revision;
1322                 }
1323             }
1324         }
1325     }
1326
1327     if (mode != CHANGES_SLOW) {
1328         // clear information about all items that we recognized as deleted
1329         ConfigProps props;
1330         trackingNode.readProperties(props);
1331
1332         BOOST_FOREACH(const StringPair &mapping, props) {
1333             const string &uid(mapping.first);
1334             if (getAllItems().find(uid) == getAllItems().end()) {
1335                 addItem(uid, DELETED);
1336                 trackingNode.removeProperty(uid);
1337             }
1338         }
1339
1340         // now update tracking node
1341         BOOST_FOREACH(const StringPair &update, revUpdates) {
1342             trackingNode.setProperty(update.first, update.second);
1343         }
1344     }
1345
1346     return forceSlowSync;
1347 }
1348
1349 void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode,
1350                                          const std::string &old_luid,
1351                                          const std::string &new_luid,
1352                                          const std::string &revision)
1353 {
1354     if (!needChanges()) {
1355         return;
1356     }
1357
1358     databaseModified();
1359     if (old_luid != new_luid) {
1360         trackingNode.removeProperty(old_luid);
1361     }
1362     if (new_luid.empty() || revision.empty()) {
1363         throwError(SE_HERE, "need non-empty LUID and revision string");
1364     }
1365     trackingNode.setProperty(new_luid, revision);
1366 }
1367
1368 void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode,
1369                                          const std::string &luid)
1370 {
1371     if (!needChanges()) {
1372         return;
1373     }
1374     databaseModified();
1375     trackingNode.removeProperty(luid);
1376 }
1377
1378 void SyncSourceRevisions::sleepSinceModification()
1379 {
1380     Timespec current = Timespec::monotonic();
1381     // Don't let this get interrupted by user abort.
1382     // It is needed for correct change tracking.
1383     while ((current - m_modTimeStamp).duration() < m_revisionAccuracySeconds) {
1384         Sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp).duration());
1385         current = Timespec::monotonic();
1386     }
1387 }
1388
1389 void SyncSourceRevisions::databaseModified()
1390 {
1391     m_modTimeStamp = Timespec::monotonic();
1392 }
1393
1394 void SyncSourceRevisions::init(SyncSourceRaw *raw,
1395                                SyncSourceDelete *del,
1396                                int granularity,
1397                                SyncSource::Operations &ops)
1398 {
1399     m_raw = raw;
1400     m_del = del;
1401     m_revisionAccuracySeconds = granularity;
1402     m_revisionsSet = false;
1403     m_firstCycle = false;
1404     if (raw) {
1405         ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData,
1406                                        this, _1, _2, _3);
1407     }
1408     if (raw && del) {
1409         ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData,
1410                                         this, _1, _2, _3);
1411     }
1412     ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceRevisions::sleepSinceModification,
1413                                                            this));
1414 }
1415
1416 std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey)
1417 {
1418     try {
1419         std::list<std::string> values;
1420
1421         BOOST_FOREACH(const std::string &field, m_fields) {
1422             SharedBuffer value;
1423             if (!getSynthesisAPI()->getValue(aItemKey, field, value) &&
1424                 value.size()) {
1425                 values.push_back(std::string(value.get()));
1426             }
1427         }
1428
1429         std::string description = boost::join(values, m_sep);
1430         return description;
1431     } catch (...) {
1432         // Instead of failing we log the error and ask
1433         // the caller to log the UID. That way transient
1434         // errors or errors in the logging code don't
1435         // prevent syncs.
1436         handleException();
1437         return "";
1438     }
1439 }
1440
1441 std::string SyncSourceLogging::getDescription(const string &luid)
1442 {
1443     return "";
1444 }
1445
1446 void SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
1447 {
1448     std::string description = getDescription(aItemKey);
1449     SE_LOG_INFO(getDisplayName(),
1450                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1451                 "adding",
1452                 !description.empty() ? description.c_str() : "???");
1453 }
1454
1455 void SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
1456 {
1457     std::string description = getDescription(aItemKey);
1458     SE_LOG_INFO(getDisplayName(),
1459                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1460                 "updating",
1461                 !description.empty() ? description.c_str() : aID ? aID->item : "???");
1462 }
1463
1464 void SyncSourceLogging::deleteItem(sysync::cItemID aID)
1465 {
1466     std::string description = getDescription(aID->item);
1467     SE_LOG_INFO(getDisplayName(),
1468                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1469                 "deleting",
1470                 !description.empty() ? description.c_str() : aID->item);
1471 }
1472
1473 void SyncSourceLogging::init(const std::list<std::string> &fields,
1474                              const std::string &sep,
1475                              SyncSource::Operations &ops)
1476 {
1477     m_fields = fields;
1478     m_sep = sep;
1479
1480     ops.m_insertItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::insertItemAsKey,
1481                                                              this, _2, _3));
1482     ops.m_updateItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::updateItemAsKey,
1483                                                              this, _2, _3, _4));
1484     ops.m_deleteItem.getPreSignal().connect(boost::bind(&SyncSourceLogging::deleteItem,
1485                                                         this, _2));
1486 }
1487
1488 sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
1489                                                 const char *aRemDB,
1490                                                 char **adminData)
1491 {
1492     std::string data = m_configNode->readProperty(m_adminPropertyName);
1493     *adminData = StrAlloc(StringEscape::unescape(data, '!').c_str());
1494     resetMap();
1495     return sysync::LOCERR_OK;
1496 }
1497
1498 sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
1499 {
1500     m_configNode->setProperty(m_adminPropertyName,
1501                               StringEscape::escape(adminData, '!', StringEscape::INI_VALUE));
1502
1503     // Flush here, because some calls to saveAdminData() happend
1504     // after SyncSourceAdmin::flush() (= session end).
1505     m_configNode->flush();
1506     return sysync::LOCERR_OK;
1507 }
1508
1509 bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst)
1510 {
1511     if (aFirst) {
1512         resetMap();
1513     }
1514     if (m_mappingIterator != m_mapping.end()) {
1515         entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID);
1516         ++m_mappingIterator;
1517         return true;
1518     } else {
1519         return false;
1520     }
1521 }
1522
1523 sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID)
1524 {
1525     string key, value;
1526     mapid2entry(mID, key, value);
1527
1528 #if 0
1529     StringMap::iterator it = m_mapping.find(key);
1530     if (it != m_mapping.end()) {
1531         // error, exists already
1532         return sysync::DB_Forbidden;
1533     } else {
1534         m_mapping[key] = value;
1535         return sysync::LOCERR_OK;
1536     }
1537 #else
1538     m_mapping[key] = value;
1539     m_mappingNode->clear();
1540     m_mappingNode->writeProperties(m_mapping);
1541     m_mappingNode->flush();
1542     return sysync::LOCERR_OK;
1543 #endif
1544 }
1545
1546 sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID)
1547 {
1548     string key, value;
1549     mapid2entry(mID, key, value);
1550
1551     ConfigProps::iterator it = m_mapping.find(key);
1552     if (it == m_mapping.end()) {
1553         // error, does not exist
1554         return sysync::DB_Forbidden;
1555     } else {
1556         m_mapping[key] = value;
1557         m_mappingNode->clear();
1558         m_mappingNode->writeProperties(m_mapping);
1559         m_mappingNode->flush();
1560         return sysync::LOCERR_OK;
1561     }
1562 }
1563
1564 sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID)
1565 {
1566     string key, value;
1567     mapid2entry(mID, key, value);
1568
1569     ConfigProps::iterator it = m_mapping.find(key);
1570     if (it == m_mapping.end()) {
1571         // error, does not exist
1572         return sysync::DB_Forbidden;
1573     } else {
1574         m_mapping.erase(it);
1575         m_mappingNode->clear();
1576         m_mappingNode->writeProperties(m_mapping);
1577         m_mappingNode->flush();
1578         return sysync::LOCERR_OK;
1579     }
1580 }
1581
1582 void SyncSourceAdmin::flush()
1583 {
1584     m_configNode->flush();
1585     if (m_mappingLoaded) {
1586         m_mappingNode->clear();
1587         m_mappingNode->writeProperties(m_mapping);
1588         m_mappingNode->flush();
1589     }
1590 }
1591
1592 void SyncSourceAdmin::resetMap()
1593 {
1594     m_mapping.clear();
1595     m_mappingNode->readProperties(m_mapping);
1596     m_mappingIterator = m_mapping.begin();
1597     m_mappingLoaded = true;
1598 }
1599
1600
1601 void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
1602 {
1603     key = StringPrintf("%s-%x",
1604                        StringEscape::escape(mID->localID ? mID->localID : "", '!', StringEscape::INI_WORD).c_str(),
1605                        mID->ident);
1606     if (mID->remoteID && mID->remoteID[0]) {
1607         value = StringPrintf("%s %x",
1608                              StringEscape::escape(mID->remoteID ? mID->remoteID : "", '!', StringEscape::INI_WORD).c_str(),
1609                              mID->flags);
1610     } else {
1611         value = StringPrintf("%x", mID->flags);
1612     }
1613 }
1614
1615 void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
1616 {
1617     size_t found = key.rfind('-');
1618     mID->localID = StrAlloc(StringEscape::unescape(key.substr(0,found), '!').c_str());
1619     if (found != key.npos) {
1620         mID->ident =  strtol(key.substr(found+1).c_str(), NULL, 16);
1621     } else {
1622         mID->ident = 0;
1623     }
1624     std::vector< std::string > tokens;
1625     boost::split(tokens, value, boost::is_from_range(' ', ' '));
1626     if (tokens.size() >= 2) {
1627         // if branch from mapid2entry above
1628         mID->remoteID = StrAlloc(StringEscape::unescape(tokens[0], '!').c_str());
1629         mID->flags = strtol(tokens[1].c_str(), NULL, 16);
1630     } else {
1631         // else branch from above
1632         mID->remoteID = NULL;
1633         mID->flags = strtol(tokens[0].c_str(), NULL, 16);
1634     }
1635 }
1636
1637 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1638                            const boost::shared_ptr<ConfigNode> &config,
1639                            const std::string adminPropertyName,
1640                            const boost::shared_ptr<ConfigNode> &mapping)
1641 {
1642     m_configNode = config;
1643     m_adminPropertyName = adminPropertyName;
1644     m_mappingNode = mapping;
1645     m_mappingLoaded = false;
1646
1647     ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData,
1648                                       this, _1, _2, _3);
1649     ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData,
1650                                       this, _1);
1651     if (mapping->isVolatile()) {
1652         // Don't provide map item operations. SynthesisDBPlugin will
1653         // tell the Synthesis engine not to call these (normally needed
1654         // for suspend/resume, which we don't support in volatile mode
1655         // because we don't store any meta data persistently).
1656         //
1657         // ops.m_readNextMapItem = boost::lambda::constant(false);
1658         // ops.m_insertMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1659         // ops.m_updateMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1660         // ops.m_deleteMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1661     } else {
1662         ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem,
1663                                             this, _1, _2);
1664         ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem,
1665                                           this, _1);
1666         ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem,
1667                                           this, _1);
1668         ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem,
1669                                           this, _1);
1670     }
1671     ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceAdmin::flush, this));
1672 }
1673
1674 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1675                            SyncSource *source)
1676 {
1677     init(ops,
1678          source->getProperties(true),
1679          SourceAdminDataName,
1680          source->getServerNode());
1681 }
1682
1683 void SyncSourceBlob::init(SyncSource::Operations &ops,
1684                           const std::string &dir)
1685 {
1686     m_blob.Init(getSynthesisAPI(),
1687                 getName().c_str(),
1688                 dir, "", "", "");
1689     ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this,
1690                                  _1, _2, _3, _4, _5, _6, _7);
1691     ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this,
1692                                   _1, _2, _3, _4, _5, _6, _7);
1693     ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this,
1694                                    _1, _2);
1695 }
1696
1697 void TestingSyncSource::removeAllItems()
1698 {
1699     // remove longest luids first:
1700     // for luid=UID[+RECURRENCE-ID] that will
1701     // remove children from a merged event first,
1702     // which is better supported by certain servers
1703     Items_t items = getAllItems();
1704     for (Items_t::reverse_iterator it = items.rbegin();
1705          it != items.rend();
1706          ++it) {
1707         deleteItem(*it);
1708     }
1709 }
1710
1711
1712
1713 #ifdef ENABLE_UNIT_TESTS
1714
1715 class SyncSourceTest : public CppUnit::TestFixture {
1716     CPPUNIT_TEST_SUITE(SyncSourceTest);
1717     CPPUNIT_TEST(backendsAvailable);
1718     CPPUNIT_TEST_SUITE_END();
1719
1720     void backendsAvailable()
1721     {
1722         //We expect backendsInfo() to be empty if !ENABLE_MODULES
1723         //Otherwise, there should be at least some backends.
1724 #ifdef ENABLE_MODULES
1725         CPPUNIT_ASSERT( !SyncSource::backendsInfo().empty() );
1726 #endif
1727     }
1728 };
1729
1730 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncSourceTest);
1731
1732 #endif // ENABLE_UNIT_TESTS
1733
1734
1735 SE_END_CXX
1736