Imported Upstream version 1.0beta3
[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 #include <syncevo/SafeConfigNode.h>
30
31 #include <syncevo/SynthesisEngine.h>
32 #include <synthesis/SDK_util.h>
33 #include <synthesis/sync_dbapidef.h>
34
35 #include <boost/bind.hpp>
36 #include <boost/algorithm/string/join.hpp>
37 #include <boost/algorithm/string/split.hpp>
38 #include <boost/algorithm/string/classification.hpp>
39 #include <boost/algorithm/string/predicate.hpp>
40
41 #include <ctype.h>
42 #include <errno.h>
43 #include <unistd.h>
44
45 #include <fstream>
46 #include <iostream>
47
48 #include <syncevo/declarations.h>
49 SE_BEGIN_CXX
50
51 void SyncSourceBase::throwError(const string &action, int error)
52 {
53     throwError(action + ": " + strerror(error));
54 }
55
56 void SyncSourceBase::throwError(const string &failure)
57 {
58     SyncContext::throwError(string(getName()) + ": " + failure);
59 }
60
61 SyncMLStatus SyncSourceBase::handleException()
62 {
63     SyncMLStatus res = Exception::handle(this);
64     return res == STATUS_FATAL ?
65         STATUS_DATASTORE_FAILURE :
66         res;
67 }
68
69 void SyncSourceBase::messagev(Level level,
70                               const char *prefix,
71                               const char *file,
72                               int line,
73                               const char *function,
74                               const char *format,
75                               va_list args)
76 {
77     string newprefix = getName();
78     if (prefix) {
79         newprefix += ": ";
80         newprefix += prefix;
81     }
82     LoggerBase::instance().messagev(level, newprefix.c_str(),
83                                     file, line, function,
84                                     format, args);
85 }
86
87 void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
88 {
89     stringstream xmlstream;
90     SynthesisInfo info;
91
92     getSynthesisInfo(info, fragments);
93
94     xmlstream <<
95         "      <plugin_module>SyncEvolution</plugin_module>\n"
96         "      <plugin_datastoreadmin>" <<
97         (serverModeEnabled() ? "yes" : "no") <<
98         "</plugin_datastoreadmin>\n"
99         "      <fromremoteonlysupport> yes </fromremoteonlysupport>\n"
100         "\n"
101         "      <!-- General datastore settings for all DB types -->\n"
102         "\n"
103         "      <!-- if this is set to 'yes', SyncML clients can only read\n"
104         "           from the database, but make no modifications -->\n"
105         "      <readonly>no</readonly>\n"
106         "\n"
107         "      <!-- conflict strategy: Newer item wins\n"
108         "           You can set 'server-wins' or 'client-wins' as well\n"
109         "           if you want to give one side precedence\n"
110         "      -->\n"
111         "      <conflictstrategy>newer-wins</conflictstrategy>\n"
112         "\n"
113         "      <!-- on slowsync: duplicate items that are not fully equal\n"
114         "           You can set this to 'newer-wins' as well to avoid\n"
115         "           duplicates as much as possible\n"
116         "      -->\n"
117         "      <slowsyncstrategy>duplicate</slowsyncstrategy>\n"
118         "\n"
119         "      <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
120         "      <datacharset>UTF-8</datacharset>\n"
121         "      <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
122         "      <datalineends>unix</datalineends>\n"
123         "\n"
124         "      <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
125         "           rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
126         "      <datatimezone>SYSTEM</datatimezone>\n"
127         "\n"
128         "      <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
129         "           we must make sure this identifier is stored (and not only the sync time) -->\n"
130         "      <storesyncidentifiers>yes</storesyncidentifiers>\n"
131         "\n";
132     
133     xmlstream <<
134         "      <!-- Mapping of the fields to the fieldlist -->\n"
135         "      <fieldmap fieldlist='" << info.m_fieldlist << "'>\n";
136     if (!info.m_profile.empty()) {
137         xmlstream <<
138             "        <initscript><![CDATA[\n"
139             "           string itemdata;\n"
140             "        ]]></initscript>\n"
141             "        <beforewritescript><![CDATA[\n";
142         if(!info.m_beforeWriteScript.empty()) {
143             xmlstream << 
144                 "           " << info.m_beforeWriteScript << "\n";
145         }
146         xmlstream <<
147             "           itemdata = MAKETEXTWITHPROFILE(" << info.m_profile << ", \"" << info.m_backendRule << "\");\n"
148             "        ]]></beforewritescript>\n"
149             "        <afterreadscript><![CDATA[\n"
150             "           PARSETEXTWITHPROFILE(itemdata, " << info.m_profile << ", \"" << info.m_backendRule << "\");\n";
151         if(!info.m_afterReadScript.empty()) {
152             xmlstream << 
153                 "           " << info.m_afterReadScript<< "\n";
154         }
155         xmlstream <<
156             "        ]]></afterreadscript>\n"
157             "        <map name='data' references='itemdata' type='string'/>\n";
158     }
159     xmlstream << 
160         "        <automap/>\n"
161         "      </fieldmap>\n"
162         "\n";
163
164     xmlstream <<
165         "      <!-- datatypes supported by this datastore -->\n"
166         "      <typesupport>\n" <<
167         info.m_datatypes <<
168         "      </typesupport>\n";
169
170     xml = xmlstream.str();
171 }
172
173 string SyncSourceBase::getNativeDatatypeName()
174 {
175     SynthesisInfo info;
176     XMLConfigFragments fragments;
177     getSynthesisInfo(info, fragments);
178     return info.m_native;
179 }
180
181 SDKInterface *SyncSource::getSynthesisAPI() const
182 {
183     return m_synthesisAPI.empty() ?
184         NULL :
185         static_cast<SDKInterface *>(m_synthesisAPI[m_synthesisAPI.size() - 1]);
186 }
187
188 void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI)
189 {
190     m_synthesisAPI.push_back(synthesisAPI);
191 }
192
193 void SyncSource::popSynthesisAPI() {
194     m_synthesisAPI.pop_back();
195 }
196
197 SourceRegistry &SyncSource::getSourceRegistry()
198 {
199     static SourceRegistry sourceRegistry;
200     return sourceRegistry;
201 }
202
203 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
204                                        bool enabled,
205                                        Create_t create,
206                                        const string &typeDescr,
207                                        const Values &typeValues) :
208     m_shortDescr(shortDescr),
209     m_enabled(enabled),
210     m_create(create),
211     m_typeDescr(typeDescr),
212     m_typeValues(typeValues)
213 {
214     SourceRegistry &registry(SyncSource::getSourceRegistry());
215
216     // insert sorted by description to have deterministic ordering
217     for(SourceRegistry::iterator it = registry.begin();
218         it != registry.end();
219         ++it) {
220         if ((*it)->m_shortDescr > shortDescr) {
221             registry.insert(it, this);
222             return;
223         }
224     }
225     registry.push_back(this);
226 }
227
228 SyncSource *const RegisterSyncSource::InactiveSource = (SyncSource *)1;
229
230 TestRegistry &SyncSource::getTestRegistry()
231 {
232     static TestRegistry testRegistry;
233     return testRegistry;
234 }
235
236 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
237     m_configName(configName),
238     m_testCaseName(testCaseName)
239 {
240     SyncSource::getTestRegistry().push_back(this);
241 }
242
243 static class ScannedModules {
244 public:
245     ScannedModules() {
246 #ifdef ENABLE_MODULES
247         list<pair <string, boost::shared_ptr<ReadDir> > > dirs;
248         /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search
249         backends from this path instead. */
250         string backend_dir (SYNCEVO_BACKEND);
251         char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR");
252         if (backend_env && strlen(backend_env)){
253             backend_dir = backend_env;
254         }
255         boost::shared_ptr<ReadDir> dir (new ReadDir (backend_dir, false));
256         string dirpath (backend_dir);
257         // scan directories for matching module names
258         do {
259             debug<<"Scanning backend libraries in " <<dirpath <<endl;
260             BOOST_FOREACH (const string &entry, *dir) {
261                 void *dlhandle;
262                 if (isDir (dirpath + '/' + entry)) {
263                     /* This is a 2-level dir, this corresponds to loading
264                      * backends from current building directory. The library
265                      * should reside in .libs sub directory.*/
266                     string path = dirpath + '/' + entry +"/.libs";
267                     if (isDir (path)) {
268                         boost::shared_ptr<ReadDir> subdir (new ReadDir (path, false));
269                         dirs.push_back (make_pair(path, subdir));
270                     }
271                     continue;
272                 }
273                 if (entry.rfind(".so") == entry.length()-3){
274
275                     // Open the shared object so that backend can register
276                     // itself. We keep that pointer, so never close the
277                     // module!
278                     string fullpath = dirpath + '/' + entry;
279                     fullpath = normalizePath(fullpath);
280                     dlhandle = dlopen(fullpath.c_str(), RTLD_NOW|RTLD_GLOBAL);
281                     // remember which modules were found and which were not
282                     if (dlhandle) {
283                         debug<<"Loading backend library "<<entry<<endl;
284                         info<<"Loading backend library "<<fullpath<<endl;
285                         m_available.push_back(entry);
286                     } else {
287                         debug<<"Loading backend library "<<entry<<"failed "<< dlerror()<<endl;
288                     }
289                 }
290             }
291             if (!dirs.empty()){
292                 dirpath = dirs.front().first;
293                 dir = dirs.front().second;
294                 dirs.pop_front();
295             } else {
296                 break;
297             }
298         } while (true);
299 #endif
300     }
301     list<string> m_available;
302     std::ostringstream debug, info;
303 } scannedModules;
304
305 string SyncSource::backendsInfo() {
306     return scannedModules.info.str();
307 }
308 string SyncSource::backendsDebug() {
309     return scannedModules.debug.str();
310 }
311
312 SyncSource *SyncSource::createSource(const SyncSourceParams &params, bool error, SyncConfig *config)
313 {
314     string sourceTypeString = getSourceTypeString(params.m_nodes);
315     SourceType sourceType = getSourceType(params.m_nodes);
316
317     if (sourceType.m_backend == "virtual") {
318         SyncSource *source = NULL;
319         source = new VirtualSyncSource(params, config);
320         if (error && !source) {
321             SyncContext::throwError(params.m_name + ": virtual source cannot be instantiated");
322         }
323         return source;
324     }
325
326     const SourceRegistry &registry(getSourceRegistry());
327     BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
328         SyncSource *source = sourceInfos->m_create(params);
329         if (source) {
330             if (source == RegisterSyncSource::InactiveSource) {
331                 SyncContext::throwError(params.m_name + ": access to " + sourceInfos->m_shortDescr +
332                                                 " not enabled, therefore type = " + sourceTypeString + " not supported");
333             }
334             return source;
335         }
336     }
337
338     if (error) {
339         string problem = params.m_name + ": type '" + sourceTypeString + "' not supported";
340         if (scannedModules.m_available.size()) {
341             problem += " by any of the backends (";
342             problem += boost::join(scannedModules.m_available, ", ");
343             problem += ")";
344         }
345         SyncContext::throwError(problem);
346     }
347
348     return NULL;
349 }
350
351 SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error,
352                                             const char *prefix)
353 {
354     SyncConfig config("testing@client-test");
355     SyncSourceNodes nodes = config.getSyncSourceNodes(name);
356     SyncSourceParams params(name, nodes);
357     PersistentSyncSourceConfig sourceconfig(name, nodes);
358     sourceconfig.setSourceType(type);
359     if (prefix) {
360         sourceconfig.setDatabaseID(string(prefix) + name + "_1");
361     }
362     return createSource(params, error);
363 }
364
365 VirtualSyncSource::VirtualSyncSource(const SyncSourceParams &params, SyncConfig *config) :
366     DummySyncSource(params)
367 {
368     if (config) {
369         BOOST_FOREACH(std::string name, getMappedSources()) {
370             SyncSourceNodes source = config->getSyncSourceNodes(name);
371             SyncSourceParams params(name, source);
372             boost::shared_ptr<SyncSource> syncSource(createSource(params, true, config));
373             m_sources.push_back(syncSource);
374         }
375     }
376 }
377
378 void VirtualSyncSource::open()
379 {
380     getDataTypeSupport();
381     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
382         source->open();
383     }
384 }
385
386 void VirtualSyncSource::close()
387 {
388     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
389         source->close();
390     }
391 }
392
393 std::vector<std::string> VirtualSyncSource::getMappedSources()
394 {
395     std::string evoSyncSource = getDatabaseID();
396     std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
397     return mappedSources;
398 }
399
400 std::string VirtualSyncSource::getDataTypeSupport()
401 {
402     string datatypes;
403     SourceType sourceType = getSourceType();
404     string type = sourceType.m_format;
405
406     datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
407     return datatypes;
408 }
409
410 SyncSource::Databases VirtualSyncSource::getDatabases()
411 {
412     SyncSource::Databases dbs;
413     BOOST_FOREACH (boost::shared_ptr<SyncSource> &source, m_sources) {
414         SyncSource::Databases sub = source->getDatabases();
415         if (sub.empty()) {
416             return dbs;
417         }
418     }
419     Database db ("calendar+todo", "");
420     dbs.push_back (db);
421     return dbs;
422 }
423
424 void SyncSourceSession::init(SyncSource::Operations &ops)
425 {
426     ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2);
427     ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2);
428 }
429
430 sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken)
431 {
432     beginSync(lastToken ? lastToken : "",
433               resumeToken ? resumeToken : "");
434     return sysync::LOCERR_OK;
435 }
436
437 sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken)
438 {
439     std::string token = endSync(success);
440     *newToken = StrAlloc(token.c_str());
441     return sysync::LOCERR_OK;
442 }
443
444 void SyncSourceChanges::init(SyncSource::Operations &ops)
445 {
446     ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3);
447 }
448
449 SyncSourceChanges::SyncSourceChanges() :
450     m_first(true)
451 {
452 }
453
454 bool SyncSourceChanges::addItem(const string &luid, State state)
455 {
456     pair<Items_t::iterator, bool> res = m_items[state].insert(luid);
457     return res.second;
458 }
459
460 sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID,
461                                             sysync::sInt32 *aStatus,
462                                             bool aFirst)
463 {
464     if (m_first || aFirst) {
465         m_it = m_items[ANY].begin();
466         m_first = false;
467     }
468
469     if (m_it == m_items[ANY].end()) {
470         *aStatus = sysync::ReadNextItem_EOF;
471     } else {
472         const string &luid = *m_it;
473
474         if (m_items[NEW].find(luid) != m_items[NEW].end() ||
475             m_items[UPDATED].find(luid) != m_items[UPDATED].end()) {
476             *aStatus = sysync::ReadNextItem_Changed;
477         } else {
478             *aStatus = sysync::ReadNextItem_Unchanged;
479         }
480         aID->item = StrAlloc(luid.c_str());
481         ++m_it;
482     }
483
484     return sysync::LOCERR_OK;
485 }
486
487 void SyncSourceDelete::init(SyncSource::Operations &ops)
488 {
489     ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1);
490 }
491
492 sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID)
493 {
494     deleteItem(aID->item);
495     incrementNumDeleted();
496     return sysync::LOCERR_OK;
497 }
498
499
500 void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info,
501                                            XMLConfigFragments &fragments)
502 {
503     string type = getMimeType();
504
505     if (type == "text/x-vcard") {
506         info.m_native = "vCard21";
507         info.m_fieldlist = "contacts";
508         info.m_profile = "\"vCard\", 1";
509         info.m_datatypes =
510             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
511             "        <use datatype='vCard30' mode='rw'/>\n";
512     } else if (type == "text/vcard") {
513         info.m_native = "vCard30";
514         info.m_fieldlist = "contacts";
515         info.m_profile = "\"vCard\", 2";
516         info.m_datatypes =
517             "        <use datatype='vCard21' mode='rw'/>\n"
518             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
519     } else if (type == "text/x-calendar" || type == "text/x-vcalendar") {
520         info.m_native = "vCalendar10";
521         info.m_fieldlist = "calendar";
522         info.m_profile = "\"vCalendar\", 1";
523         info.m_datatypes =
524             "        <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
525             "        <use datatype='iCalendar20' mode='rw'/>\n";
526         /**
527          * here are two default implementations. If user wants to reset it,
528          * just implement its own getSynthesisInfo. If user wants to use this default
529          * implementations and its new scripts, it is possible to append its implementations
530          * to info.m_afterReadScript and info.m_beforeWriteScript.
531          */
532         info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
533         info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n";
534     } else if (type == "text/calendar") {
535         info.m_native = "iCalendar20";
536         info.m_fieldlist = "calendar";
537         info.m_profile = "\"vCalendar\", 2";
538         info.m_datatypes =
539             "        <use datatype='vCalendar10' mode='rw'/>\n"
540             "        <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
541     } else if (type == "text/plain") {
542         info.m_fieldlist = "Note";
543         info.m_profile = "\"Note\", 2";
544     } else {
545         throwError(string("default MIME type not supported: ") + type);
546     }
547
548     SourceType sourceType = getSourceType();
549     if (!sourceType.m_format.empty()) {
550         type = sourceType.m_format;
551     }
552     info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
553 }
554
555 std::string SyncSourceBase::getDataTypeSupport(const std::string &type,
556                                                bool forceFormat)
557 {
558     std::string datatypes;
559
560     if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
561         datatypes =
562             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
563         if (!forceFormat) {
564             datatypes +=
565                 "        <use datatype='vCard30' mode='rw'/>\n";
566         }
567     } else if (type == "text/vcard:3.0" || type == "text/vcard") {
568         datatypes =
569             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
570         if (!forceFormat) {
571             datatypes +=
572                 "        <use datatype='vCard21' mode='rw'/>\n";
573         }
574     } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar"
575              || type == "text/x-calendar:1.0" || type == "text/x-calendar") {
576         datatypes =
577             "        <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
578         if (!forceFormat) {
579             datatypes +=
580                 "        <use datatype='icalendar20' mode='rw'/>\n";
581         }
582     } else if (type == "text/calendar:2.0" || type == "text/calendar") {
583         datatypes =
584             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
585         if (!forceFormat) {
586             datatypes +=
587                 "        <use datatype='vcalendar10' mode='rw'/>\n";
588         }
589     } else if (type == "text/plain:1.0" || type == "text/plain") {
590         // note10 are the same as note11, so ignore force format
591         datatypes =
592             "        <use datatype='note10' mode='rw' preferred='yes'/>\n"
593             "        <use datatype='note11' mode='rw'/>\n";
594     } else if (type.empty()) {
595         throwError("no MIME type configured");
596     } else {
597         throwError(string("configured MIME type not supported: ") + type);
598     }
599
600     return datatypes;
601 }
602
603 sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
604 {
605     std::string item;
606
607     readItem(aID->item, item);
608     TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size());
609     return res;
610 }
611
612 sysync::TSyError SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
613 {
614     SharedBuffer data;
615     TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
616
617     if (!res) {
618         InsertItemResult inserted =
619             insertItem(!aID ? "" : aID->item, data.get());
620         newID->item = StrAlloc(inserted.m_luid.c_str());
621     }
622
623     return res;
624 }
625
626 void SyncSourceSerialize::init(SyncSource::Operations &ops)
627 {
628     ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey,
629                                       this, _1, _2);
630     ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
631                                         this, _1, (sysync::cItemID)NULL, _2);
632     ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
633                                         this, _1, _2, _3);
634 }
635
636 /**
637  * Mapping from Hash() value to file.
638  */
639 class ItemCache
640 {
641 public:
642 #ifdef USE_SHA256
643     typedef std::string Hash_t;
644     Hash_t hashFunc(const std::string &data) { return SHA_256(data); }
645 #else
646     typedef unsigned long Hash_t;
647     Hash_t hashFunc(const std::string &data) { return Hash(data); }
648 #endif
649     typedef unsigned long Counter_t;
650
651     /** mark the algorithm used for the hash via different suffices */
652     static const char *m_hashSuffix;
653
654     /**
655      * Collect information about stored hashes. Provides
656      * access to file name via hash.
657      *
658      * If no hashes were written (as in an old SyncEvoltion
659      * version), we could read the files to recreate the
660      * hashes. This is not done because it won't occur
661      * often enough.
662      *
663      * Hashes are also not verified. Users should better
664      * not edit them or file contents...
665      *
666      * @param oldBackup     existing backup to read; may be empty
667      */
668     void init(const SyncSource::Operations::ConstBackupInfo &oldBackup)
669     {
670         m_hash2counter.clear();
671         m_dirname = oldBackup.m_dirname;
672         if (m_dirname.empty() || !oldBackup.m_node) {
673             return;
674         }
675
676         long numitems;
677         if (!oldBackup.m_node->getProperty("numitems", numitems)) {
678             return;
679         }
680         for (long counter = 1; counter <= numitems; counter++) {
681             stringstream key;
682             key << counter << m_hashSuffix;
683             Hash_t hash;
684             if (oldBackup.m_node->getProperty(key.str(), hash)) {
685                 m_hash2counter[hash] = counter;
686             }
687         }
688     }
689
690     /**
691      * create file name for a specific hash, empty if no such hash
692      */
693     string getFilename(Hash_t hash)
694     {
695         Map_t::const_iterator it = m_hash2counter.find(hash);
696         if (it != m_hash2counter.end()) {
697             stringstream dirname;
698             dirname << m_dirname << "/" << it->second;
699             return dirname.str();
700         } else {
701             return "";
702         }
703     }
704
705 private:
706     typedef std::map<Hash_t, Counter_t> Map_t;
707     Map_t m_hash2counter;
708     string m_dirname;
709 };
710
711 const char *ItemCache::m_hashSuffix =
712 #ifdef USE_SHA256
713     "-sha256"
714 #else
715     "-hash"
716 #endif
717 ;
718
719 void SyncSourceRevisions::initRevisions()
720 {
721     if (!m_revisionsSet) {
722         listAllItems(m_revisions);
723         m_revisionsSet = true;
724     }
725 }
726
727
728 void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
729                                      const SyncSource::Operations::BackupInfo &newBackup,
730                                      BackupReport &report)
731 {
732     ItemCache cache;
733     cache.init(oldBackup);
734
735     bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
736     RevisionMap_t buffer;
737     RevisionMap_t *revisions;
738     if (startOfSync) {
739         initRevisions();
740         revisions = &m_revisions;
741     } else {
742         listAllItems(buffer);
743         revisions = &buffer;
744     }
745
746     unsigned long counter = 1;
747     string item;
748     errno = 0;
749     BOOST_FOREACH(const StringPair &mapping, *revisions) {
750         const string &uid = mapping.first;
751         const string &rev = mapping.second;
752         m_raw->readItemRaw(uid, item);
753
754         stringstream filename;
755         filename << newBackup.m_dirname << "/" << counter;
756
757         ItemCache::Hash_t hash = cache.hashFunc(item);
758         string oldfilename = cache.getFilename(hash);
759         if (!oldfilename.empty()) {
760             // found old file with same content, reuse it via hardlink
761             if (link(oldfilename.c_str(), filename.str().c_str())) {
762                 // Hard linking failed. Record this, then continue
763                 // by ignoring the old file.
764                 SE_LOG_DEBUG(NULL, NULL, "hard linking old %s new %s: %s",
765                              oldfilename.c_str(),
766                              filename.str().c_str(),
767                              strerror(errno));
768                 oldfilename.clear();
769             }
770         }
771
772         if (oldfilename.empty()) {
773             // write new file instead of reusing old one
774             ofstream out(filename.str().c_str());
775             out.write(item.c_str(), item.size());
776             out.close();
777             if (out.fail()) {
778                 throwError(string("error writing ") + filename.str() + ": " + strerror(errno));
779             }
780         }
781
782         stringstream key;
783         key << counter << "-uid";
784         newBackup.m_node->setProperty(key.str(), uid);
785         // clear() does not remove the existing content, which was
786         // intended here. This should have been key.str(""). As a
787         // result, keys for -rev are longer than intended because they
788         // start with the -uid part. We cannot change it now, because
789         // that would break compatibility with nodes that use the
790         // older, longer keys for -rev.
791         key.clear();
792         key << counter << "-rev";
793         newBackup.m_node->setProperty(key.str(), rev);
794         key.str("");
795         key << counter << ItemCache::m_hashSuffix;
796         newBackup.m_node->setProperty(key.str(), hash);
797
798         counter++;
799     }
800
801     stringstream value;
802     value << counter - 1;
803     newBackup.m_node->setProperty("numitems", value.str());
804     newBackup.m_node->flush();
805
806     report.setNumItems(counter - 1);
807 }
808
809 void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
810                                       bool dryrun,
811                                       SyncSourceReport &report)
812 {
813     RevisionMap_t revisions;
814     listAllItems(revisions);
815
816     long numitems;
817     string strval;
818     strval = oldBackup.m_node->readProperty("numitems");
819     stringstream stream(strval);
820     stream >> numitems;
821
822     for (long counter = 1; counter <= numitems; counter++) {
823         stringstream key;
824         key << counter << "-uid";
825         string uid = oldBackup.m_node->readProperty(key.str());
826         key.clear();
827         key << counter << "-rev";
828         string rev = oldBackup.m_node->readProperty(key.str());
829         RevisionMap_t::iterator it = revisions.find(uid);
830         report.incrementItemStat(report.ITEM_LOCAL,
831                                  report.ITEM_ANY,
832                                  report.ITEM_TOTAL);
833         if (it != revisions.end() &&
834             it->second == rev) {
835             // item exists in backup and database with same revision:
836             // nothing to do
837         } else {
838             // add or update, so need item
839             stringstream filename;
840             filename << oldBackup.m_dirname << "/" << counter;
841             string data;
842             if (!ReadFile(filename.str(), data)) {
843                 throwError(StringPrintf("restoring %s from %s failed: could not read file",
844                                         uid.c_str(),
845                                         filename.str().c_str()));
846             }
847             // TODO: it would be nicer to recreate the item
848             // with the original revision. If multiple peers
849             // synchronize against us, then some of them
850             // might still be in sync with that revision. By
851             // updating the revision here we force them to
852             // needlessly receive an update.
853             //
854             // For the current peer for which we restore this is
855             // avoided by the revision check above: unchanged
856             // items aren't touched.
857             SyncSourceReport::ItemState state =
858                 it == revisions.end() ?
859                 SyncSourceReport::ITEM_ADDED :   // not found in database, create anew
860                 SyncSourceReport::ITEM_UPDATED;  // found, update existing item
861             try {
862                 report.incrementItemStat(report.ITEM_LOCAL,
863                                          state,
864                                          report.ITEM_TOTAL);
865                 if (!dryrun) {
866                     m_raw->insertItemRaw(it == revisions.end() ? "" : uid,
867                                          data);
868                 }
869             } catch (...) {
870                 report.incrementItemStat(report.ITEM_LOCAL,
871                                          state,
872                                          report.ITEM_REJECT);
873                 throw;
874             }
875         }
876
877         // remove handled item from revision list so
878         // that when we are done, the only remaining
879         // items listed there are the ones which did
880         // no exist in the backup
881         if (it != revisions.end()) {
882             revisions.erase(it);
883         }
884     }
885
886     // now remove items that were not in the backup
887     BOOST_FOREACH(const StringPair &mapping, revisions) {
888         try {
889             report.incrementItemStat(report.ITEM_LOCAL,
890                                      report.ITEM_REMOVED,
891                                      report.ITEM_TOTAL);
892             if (!dryrun) {
893                 m_del->deleteItem(mapping.first);
894             }
895         } catch(...) {
896             report.incrementItemStat(report.ITEM_LOCAL,
897                                      report.ITEM_REMOVED,
898                                      report.ITEM_REJECT);
899             throw;
900         }
901     }
902 }
903
904 void SyncSourceRevisions::detectChanges(ConfigNode &trackingNode)
905 {
906     initRevisions();
907
908     BOOST_FOREACH(const StringPair &mapping, m_revisions) {
909         const string &uid = mapping.first;
910         const string &revision = mapping.second;
911
912         // always remember the item, need full list
913         addItem(uid);
914
915         string serverRevision(trackingNode.readProperty(uid));
916         if (!serverRevision.size()) {
917             addItem(uid, NEW);
918             trackingNode.setProperty(uid, revision);
919         } else {
920             if (revision != serverRevision) {
921                 addItem(uid, UPDATED);
922                 trackingNode.setProperty(uid, revision);
923             }
924         }
925     }
926
927     // clear information about all items that we recognized as deleted
928     ConfigProps props;
929     trackingNode.readProperties(props);
930
931     BOOST_FOREACH(const StringPair &mapping, props) {
932         const string &uid(mapping.first);
933         if (getAllItems().find(uid) == getAllItems().end()) {
934             addItem(uid, DELETED);
935             trackingNode.removeProperty(uid);
936         }
937     }
938 }
939
940 void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode,
941                                          const std::string &old_luid,
942                                          const std::string &new_luid,
943                                          const std::string &revision)
944 {
945     databaseModified();
946     if (old_luid != new_luid) {
947         trackingNode.removeProperty(old_luid);
948     }
949     if (new_luid.empty() || revision.empty()) {
950         throwError("need non-empty LUID and revision string");
951     }
952     trackingNode.setProperty(new_luid, revision);
953 }
954
955 void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode,
956                                          const std::string &luid)
957 {
958     databaseModified();
959     trackingNode.removeProperty(luid);
960 }
961
962 void SyncSourceRevisions::sleepSinceModification()
963 {
964     time_t current = time(NULL);
965     while (current - m_modTimeStamp < m_revisionAccuracySeconds) {
966         sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp));
967         current = time(NULL);
968     }
969 }
970
971 void SyncSourceRevisions::databaseModified()
972 {
973     m_modTimeStamp = time(NULL);
974 }
975
976 void SyncSourceRevisions::init(SyncSourceRaw *raw,
977                                SyncSourceDelete *del,
978                                int granularity,
979                                SyncSource::Operations &ops)
980 {
981     m_raw = raw;
982     m_del = del;
983     m_modTimeStamp = 0;
984     m_revisionAccuracySeconds = granularity;
985     m_revisionsSet = false;
986     if (raw) {
987         ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData,
988                                        this, _1, _2, _3);
989     }
990     if (raw && del) {
991         ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData,
992                                         this, _1, _2, _3);
993     }
994     ops.m_endSession.push_back(boost::bind(&SyncSourceRevisions::sleepSinceModification,
995                                            this));
996 }
997
998 std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey)
999 {
1000     try {
1001         std::list<std::string> values;
1002
1003         BOOST_FOREACH(const std::string &field, m_fields) {
1004             SharedBuffer value;
1005             if (!getSynthesisAPI()->getValue(aItemKey, field, value) &&
1006                 value.size()) {
1007                 values.push_back(std::string(value.get()));
1008             }
1009         }
1010
1011         std::string description = boost::join(values, m_sep);
1012         return description;
1013     } catch (...) {
1014         // Instead of failing we log the error and ask
1015         // the caller to log the UID. That way transient
1016         // errors or errors in the logging code don't
1017         // prevent syncs.
1018         handleException();
1019         return "";
1020     }
1021 }
1022
1023 std::string SyncSourceLogging::getDescription(const string &luid)
1024 {
1025     return "";
1026 }
1027
1028 sysync::TSyError SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID, const boost::function<SyncSource::Operations::InsertItemAsKey_t> &parent)
1029 {
1030     std::string description = getDescription(aItemKey);
1031     SE_LOG_INFO(this, NULL,
1032                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1033                 "adding",
1034                 !description.empty() ? description.c_str() : "???");
1035     if (parent) {
1036         return parent(aItemKey, newID);
1037     } else {
1038         return sysync::LOCERR_NOTIMP;
1039     }
1040 }
1041
1042 sysync::TSyError SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID, const boost::function<SyncSource::Operations::UpdateItemAsKey_t> &parent)
1043 {
1044     std::string description = getDescription(aItemKey);
1045     SE_LOG_INFO(this, NULL,
1046                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1047                 "updating",
1048                 !description.empty() ? description.c_str() : aID ? aID->item : "???");
1049     if (parent) {
1050         return parent(aItemKey, aID, newID);
1051     } else {
1052         return sysync::LOCERR_NOTIMP;
1053     }
1054 }
1055
1056 sysync::TSyError SyncSourceLogging::deleteItem(sysync::cItemID aID, const boost::function<SyncSource::Operations::DeleteItem_t> &parent)
1057 {
1058     std::string description = getDescription(aID->item);
1059     SE_LOG_INFO(this, NULL,
1060                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1061                 "deleting",
1062                 !description.empty() ? description.c_str() : aID->item);
1063     if (parent) {
1064         return parent(aID);
1065     } else {
1066         return sysync::LOCERR_NOTIMP;
1067     }
1068 }
1069
1070 void SyncSourceLogging::init(const std::list<std::string> &fields,
1071                              const std::string &sep,
1072                              SyncSource::Operations &ops)
1073 {
1074     m_fields = fields;
1075     m_sep = sep;
1076
1077     ops.m_insertItemAsKey = boost::bind(&SyncSourceLogging::insertItemAsKey,
1078                                         this, _1, _2, ops.m_insertItemAsKey);
1079     ops.m_updateItemAsKey = boost::bind(&SyncSourceLogging::updateItemAsKey,
1080                                         this, _1, _2, _3, ops.m_updateItemAsKey);
1081     ops.m_deleteItem = boost::bind(&SyncSourceLogging::deleteItem,
1082                                    this, _1, ops.m_deleteItem);
1083 }
1084
1085 sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
1086                                                 const char *aRemDB,
1087                                                 char **adminData)
1088 {
1089     std::string data = m_configNode->readProperty(m_adminPropertyName);
1090     *adminData = StrAlloc(SafeConfigNode::unescape(data).c_str());
1091     resetMap();
1092     return sysync::LOCERR_OK;
1093 }
1094
1095 sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
1096 {
1097     m_configNode->setProperty(m_adminPropertyName,
1098                               SafeConfigNode::escape(adminData, false, false));
1099
1100     // Flush here, because some calls to saveAdminData() happend
1101     // after SyncSourceAdmin::flush() (= session end).
1102     m_configNode->flush();
1103     return sysync::LOCERR_OK;
1104 }
1105
1106 bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst)
1107 {
1108     if (aFirst) {
1109         resetMap();
1110     }
1111     if (m_mappingIterator != m_mapping.end()) {
1112         entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID);
1113         ++m_mappingIterator;
1114         return true;
1115     } else {
1116         return false;
1117     }
1118 }
1119
1120 sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID)
1121 {
1122     string key, value;
1123     mapid2entry(mID, key, value);
1124
1125 #if 0
1126     StringMap::iterator it = m_mapping.find(key);
1127     if (it != m_mapping.end()) {
1128         // error, exists already
1129         return sysync::DB_Forbidden;
1130     } else {
1131         m_mapping[key] = value;
1132         return sysync::LOCERR_OK;
1133     }
1134 #else
1135     m_mapping[key] = value;
1136     m_mappingNode->clear();
1137     m_mappingNode->writeProperties(m_mapping);
1138     m_mappingNode->flush();
1139     return sysync::LOCERR_OK;
1140 #endif
1141 }
1142
1143 sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID)
1144 {
1145     string key, value;
1146     mapid2entry(mID, key, value);
1147
1148     StringMap::iterator it = m_mapping.find(key);
1149     if (it == m_mapping.end()) {
1150         // error, does not exist
1151         return sysync::DB_Forbidden;
1152     } else {
1153         m_mapping[key] = value;
1154         m_mappingNode->clear();
1155         m_mappingNode->writeProperties(m_mapping);
1156         m_mappingNode->flush();
1157         return sysync::LOCERR_OK;
1158     }
1159 }
1160
1161 sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID)
1162 {
1163     string key, value;
1164     mapid2entry(mID, key, value);
1165
1166     StringMap::iterator it = m_mapping.find(key);
1167     if (it == m_mapping.end()) {
1168         // error, does not exist
1169         return sysync::DB_Forbidden;
1170     } else {
1171         m_mapping.erase(it);
1172         m_mappingNode->clear();
1173         m_mappingNode->writeProperties(m_mapping);
1174         m_mappingNode->flush();
1175         return sysync::LOCERR_OK;
1176     }
1177 }
1178
1179 void SyncSourceAdmin::flush()
1180 {
1181     m_configNode->flush();
1182     if (m_mappingLoaded) {
1183         m_mappingNode->clear();
1184         m_mappingNode->writeProperties(m_mapping);
1185         m_mappingNode->flush();
1186     }
1187 }
1188
1189 void SyncSourceAdmin::resetMap()
1190 {
1191     m_mapping.clear();
1192     m_mappingNode->readProperties(m_mapping);
1193     m_mappingIterator = m_mapping.begin();
1194     m_mappingLoaded = true;
1195 }
1196
1197
1198 void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
1199 {
1200     key = StringPrintf ("%s-%x",
1201                          SafeConfigNode::escape(mID->localID ? mID->localID : "", true, false).c_str(),
1202                          mID->ident);
1203     if (mID->remoteID && mID->remoteID[0]) {
1204         value = StringPrintf("%s %x",
1205                              SafeConfigNode::escape(mID->remoteID ? mID->remoteID : "", true, false).c_str(),
1206                              mID->flags);
1207     } else {
1208         value = StringPrintf("%x", mID->flags);
1209     }
1210 }
1211
1212 void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
1213 {
1214     size_t found = key.rfind('-');
1215     mID->localID = StrAlloc(SafeConfigNode::unescape(key.substr(0,found)).c_str());
1216     if (found != key.npos) {
1217         mID->ident =  strtol(key.substr(found+1).c_str(), NULL, 16);
1218     } else {
1219         mID->ident = 0;
1220     }
1221     std::vector< std::string > tokens;
1222     boost::split(tokens, value, boost::is_from_range(' ', ' '));
1223     if (tokens.size() >= 2) {
1224         // if branch from mapid2entry above
1225         mID->remoteID = StrAlloc(SafeConfigNode::unescape(tokens[0]).c_str());
1226         mID->flags = strtol(tokens[1].c_str(), NULL, 16);
1227     } else {
1228         // else branch from above
1229         mID->remoteID = NULL;
1230         mID->flags = strtol(tokens[0].c_str(), NULL, 16);
1231     }
1232 }
1233
1234 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1235                            const boost::shared_ptr<ConfigNode> &config,
1236                            const std::string adminPropertyName,
1237                            const boost::shared_ptr<ConfigNode> &mapping)
1238 {
1239     m_configNode = config;
1240     m_adminPropertyName = adminPropertyName;
1241     m_mappingNode = mapping;
1242     m_mappingLoaded = false;
1243
1244     ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData,
1245                                       this, _1, _2, _3);
1246     ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData,
1247                                       this, _1);
1248     ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem,
1249                                         this, _1, _2);
1250     ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem,
1251                                       this, _1);
1252     ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem,
1253                                       this, _1);
1254     ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem,
1255                                       this, _1);
1256     ops.m_endSession.push_back(boost::bind(&SyncSourceAdmin::flush,
1257                                            this));
1258 }
1259
1260 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1261                            SyncSource *source)
1262 {
1263     init(ops,
1264          source->getProperties(true),
1265          SourceAdminDataName,
1266          source->getServerNode());
1267 }
1268
1269 void SyncSourceBlob::init(SyncSource::Operations &ops,
1270                           const std::string &dir)
1271 {
1272     m_blob.Init(getSynthesisAPI(),
1273                 getName(),
1274                 dir, "", "", "");
1275     ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this,
1276                                  _1, _2, _3, _4, _5, _6, _7);
1277     ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this,
1278                                   _1, _2, _3, _4, _5, _6, _7);
1279     ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this,
1280                                    _1, _2);
1281 }
1282
1283 SE_END_CXX
1284