Imported Upstream version 1.3.99.4
[platform/upstream/syncevolution.git] / src / syncevo / SyncConfig.cpp
1 /*
2  * Copyright (C) 2008-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 #include "config.h"
22
23 #include <syncevo/SyncConfig.h>
24 #include <syncevo/SyncSource.h>
25 #include <syncevo/SyncContext.h>
26 #include <syncevo/FileConfigTree.h>
27 #include <syncevo/VolatileConfigTree.h>
28 #include <syncevo/VolatileConfigNode.h>
29 #include <syncevo/DevNullConfigNode.h>
30 #include <syncevo/MultiplexConfigNode.h>
31 #include <syncevo/SingleFileConfigTree.h>
32 #include <syncevo/IniConfigNode.h>
33 #include <syncevo/Cmdline.h>
34 #include <syncevo/lcs.h>
35 #include <syncevo/ThreadSupport.h>
36 #include <test.h>
37 #include <synthesis/timeutil.h>
38
39 #include <boost/foreach.hpp>
40 #include <iterator>
41 #include <algorithm>
42 #include <functional>
43 #include <queue>
44
45 #include <unistd.h>
46 #include "config.h"
47
48 #include <syncevo/declarations.h>
49 SE_BEGIN_CXX
50
51 const char *const SourceAdminDataName = "adminData";
52
53 int ConfigVersions[CONFIG_LEVEL_MAX][CONFIG_VERSION_MAX] =
54 {
55     { CONFIG_ROOT_MIN_VERSION, CONFIG_ROOT_CUR_VERSION },
56     { CONFIG_CONTEXT_MIN_VERSION, CONFIG_CONTEXT_CUR_VERSION },
57     { CONFIG_PEER_MIN_VERSION, CONFIG_PEER_CUR_VERSION },
58 };    
59
60 std::string ConfigLevel2String(ConfigLevel level)
61 {
62     switch (level) {
63     case CONFIG_LEVEL_ROOT:
64         return "config root";
65         break;
66     case CONFIG_LEVEL_CONTEXT:
67         return "context config";
68         break;
69     case CONFIG_LEVEL_PEER:
70         return "peer config";
71         break;
72     default:
73         return StringPrintf("config level %d (?)", level);
74         break;
75     }
76 }
77
78 PropertySpecifier PropertySpecifier::StringToPropSpec(const std::string &spec, int flags)
79 {
80     PropertySpecifier res;
81
82     size_t slash = spec.find('/');
83     if (slash != spec.npos) {
84         // no normalization needed at the moment
85         res.m_source = spec.substr(0, slash);
86         slash++;
87     } else {
88         slash = 0;
89     }
90     size_t at = spec.find('@', slash);
91     if (at != spec.npos) {
92         // Context or config?
93         if (spec.find('@', at + 1) != spec.npos) {
94             // has a second @ sign, must be config name
95             res.m_config = spec.substr(at + 1);
96         } else {
97             // context, include leading @ sign
98             res.m_config = spec.substr(at);
99         }
100         if (flags & NORMALIZE_CONFIG) {
101             res.m_config = SyncConfig::normalizeConfigString(res.m_config, SyncConfig::NORMALIZE_LONG_FORMAT);
102         }
103     } else {
104         at = spec.size();
105     }
106     res.m_property = spec.substr(slash, at - slash);
107
108     return res;
109 }
110
111 std::string PropertySpecifier::toString()
112 {
113     std::string res;
114     res.reserve(m_source.size() + 1 + m_property.size() + 1 + m_config.size());
115     res += m_source;
116     if (!m_source.empty()) {
117         res += '/';
118     }
119     res += m_property;
120     if (!m_config.empty()) {
121         if (m_config[0] != '@') {
122             res += '@';
123         }
124         res += m_config;
125     }
126
127     return res;
128 }
129
130 string ConfigProperty::getName(const ConfigNode &node) const
131 {
132     if (m_names.empty()) {
133         // shouldn't happen
134         return "???";
135     }
136     if (m_names.size() == 1) {
137         // typical case for most properties
138         return m_names.front();
139     }
140     // pick the name already used in the node
141     BOOST_FOREACH(const std::string &name, m_names) {
142         string value;
143         if (node.getProperty(name, value)) {
144             return name;
145         }
146     }
147
148     // main name as fallback
149     return m_names.front();
150 }
151
152 void ConfigProperty::splitComment(const string &comment, list<string> &commentLines)
153 {
154     size_t start = 0;
155
156     while (true) {
157         size_t end = comment.find('\n', start);
158         if (end == comment.npos) {
159             commentLines.push_back(comment.substr(start));
160             break;
161         } else {
162             commentLines.push_back(comment.substr(start, end - start));
163             start = end + 1;
164         }
165     }
166 }
167
168 void ConfigProperty::throwValueError(const ConfigNode &node, const string &name, const string &value, const string &error) const
169 {
170     SyncContext::throwError(node.getName() + ": " + name + " = " + value + ": " + error);
171 }
172
173 std::string ConfigProperty::sharing2str(Sharing sharing)
174 {
175     switch (sharing) {
176     case GLOBAL_SHARING:
177         return "global";
178         break;
179     case SOURCE_SET_SHARING:
180         return "shared";
181         break;
182     case NO_SHARING:
183         return "unshared";
184         break;
185     }
186     return "???";
187 }
188
189 string SyncConfig::normalizeConfigString(const string &config, NormalizeFlags flags)
190 {
191     string normal = config;
192     boost::to_lower(normal);
193     BOOST_FOREACH(char &character, normal) {
194         if (!isprint(character) ||
195             character == '/' ||
196             character == '\\' ||
197             character == ':') {
198             character = '_';
199         }
200     }
201     if (boost::ends_with(normal, "@default")) {
202         if (flags & NORMALIZE_SHORTHAND) {
203             normal.resize(normal.size() - strlen("@default"));
204         }
205     } else if (boost::ends_with(normal, "@")) {
206         normal.resize(normal.size() - 1);
207     } else {
208         size_t at = normal.rfind('@');
209         if (at == normal.npos &&
210             !(flags & NORMALIZE_IS_NEW)) {
211             // No explicit context. Pick the first server which matches
212             // when ignoring their context. Peer list is sorted by name,
213             // therefore shorter config names (= without context) are
214             // found first, as intended.
215             BOOST_FOREACH(const StringPair &entry, getConfigs()) {
216                 string entry_peer, entry_context;
217                 splitConfigString(entry.first, entry_peer, entry_context);
218                 if (normal == entry_peer) {
219                     // found a matching, existing config, use it
220                     normal = entry.first;
221                     break;
222                 }
223             }
224         }
225         if (!(flags & NORMALIZE_SHORTHAND) && normal.find('@') == normal.npos) {
226             // explicitly include @default context specifier
227             normal += "@default";
228         }
229     }
230
231     if (normal.empty()) {
232         // default context is meant with the empty string,
233         // better make that explicit
234         normal = "@default";
235     }
236
237     return normal;
238 }
239
240 std::string SyncConfig::DeviceDescription::getFingerprint() const
241 {
242     std::string fingerprint;
243
244     /** In the case that we have the PnpInformation we prefer it over
245      *  the mutable device name. The is true even if we only found the
246      *  vendor component of the PnpInformation.
247      */
248     if (m_pnpInformation) {
249         if(m_pnpInformation->isKnownProduct())
250             fingerprint = m_pnpInformation->m_product;
251         else
252             fingerprint = m_pnpInformation->m_vendor;
253     }
254     else {
255         fingerprint = m_deviceName;
256     }
257
258     return fingerprint;
259 }
260
261 bool SyncConfig::splitConfigString(const string &config, string &peer, string &context)
262 {
263     string::size_type at = config.rfind('@');
264     if (at != config.npos) {
265         peer = config.substr(0, at);
266         context = config.substr(at + 1);
267         return true;
268     } else {
269         peer = config;
270         context = "default";
271         return false;
272     }    
273 }
274
275 static SyncConfig::ConfigWriteMode defaultConfigWriteMode()
276 {
277     return SyncContext::isStableRelease() ?
278         SyncConfig::MIGRATE_AUTOMATICALLY :
279         SyncConfig::ASK_USER_TO_MIGRATE;
280 }
281
282 SyncConfig::SyncConfig() :
283     m_layout(HTTP_SERVER_LAYOUT), // use more compact layout with shorter paths and less source nodes
284     m_configWriteMode(defaultConfigWriteMode())
285 {
286     // initialize properties
287     SyncConfig::getRegistry();
288     SyncSourceConfig::getRegistry();
289
290     m_peerPath =
291         m_contextPath = "volatile";
292     makeVolatile();
293 }
294
295 void SyncConfig::makeVolatile()
296 {
297     m_tree.reset(new VolatileConfigTree());
298     m_peerNode.reset(new VolatileConfigNode());
299     m_hiddenPeerNode = m_peerNode;
300     m_globalNode = m_peerNode;
301     m_contextNode = m_peerNode;
302     m_contextHiddenNode = m_peerNode;
303     m_props[false] = m_peerNode;
304     m_props[true] = m_peerNode;
305 }
306
307 void SyncConfig::makeEphemeral()
308 {
309     m_ephemeral = true;
310     // m_hiddenPeerNode.reset(new VolatileConfigNode());
311     // m_contextHiddenNode = m_hiddenPeerNode;
312 }
313
314 SyncConfig::SyncConfig(const string &peer,
315                        boost::shared_ptr<ConfigTree> tree,
316                        const string &redirectPeerRootPath) :
317     m_layout(SHARED_LAYOUT),
318     m_redirectPeerRootPath(redirectPeerRootPath),
319     m_configWriteMode(defaultConfigWriteMode())
320 {
321     // initialize properties
322     SyncConfig::getRegistry();
323     SyncSourceConfig::getRegistry();
324
325     string root;
326
327     m_peer = normalizeConfigString(peer);
328
329     // except for SHARED_LAYOUT (set below),
330     // everything is below the directory called like
331     // the peer
332     m_peerPath =
333         m_contextPath = 
334         m_peer;
335
336     if (tree.get() != NULL) {
337         // existing tree points into simple configuration
338         m_tree = tree;
339         m_layout = HTTP_SERVER_LAYOUT;
340         m_peerPath =
341             m_contextPath = "";
342     } else {
343         // search for configuration in various places...
344         root = getOldRoot();
345         string path = root + "/" + m_peerPath;
346         if (!access((path + "/spds/syncml/config.txt").c_str(), F_OK)) {
347             m_layout = SYNC4J_LAYOUT;
348         } else {
349             root = getNewRoot();
350             path = root + "/" + m_peerPath;
351             if (!access((path + "/config.ini").c_str(), F_OK) &&
352                 !access((path + "/sources").c_str(), F_OK) &&
353                 access((path + "/peers").c_str(), F_OK)) {
354                 m_layout = HTTP_SERVER_LAYOUT;
355             } else {
356                 // check whether config name specifies a context,
357                 // otherwise use "default"
358                 splitConfigString(m_peer, m_peerPath, m_contextPath);
359                 if (!m_peerPath.empty()) {
360                     m_peerPath = m_contextPath + "/peers/" + m_peerPath;
361                 }
362             }
363         }
364         m_tree.reset(new FileConfigTree(root,
365                                         m_peerPath.empty() ? m_contextPath : m_peerPath,
366                                         m_layout));
367     }
368
369     string path;
370     boost::shared_ptr<ConfigNode> node;
371     switch (m_layout) {
372     case SYNC4J_LAYOUT:
373         // all properties reside in the same node
374         path = m_peerPath + "/spds/syncml";
375         node = m_tree->open(path, ConfigTree::visible);
376         m_peerNode.reset(new FilterConfigNode(node));
377         m_globalNode =
378             m_contextNode = m_peerNode;
379         m_hiddenPeerNode =
380             m_contextHiddenNode =
381             m_globalHiddenNode =
382             node;
383         m_props[false] = m_peerNode;
384         m_props[true].reset(new FilterConfigNode(m_hiddenPeerNode));
385         break;
386     case HTTP_SERVER_LAYOUT: {
387         // properties which are normally considered shared are
388         // stored in the same nodes as the per-peer properties,
389         // except for global ones
390         path = "";
391         node = m_tree->open(path, ConfigTree::visible);
392         m_globalNode.reset(new FilterConfigNode(node));
393         node = m_tree->open(path, ConfigTree::hidden);
394         m_globalHiddenNode = node;
395
396         path = m_peerPath;      
397         node = m_tree->open(path, ConfigTree::visible);
398         m_peerNode.reset(new FilterConfigNode(node));
399         m_contextNode = m_peerNode;
400         m_hiddenPeerNode =
401             m_contextHiddenNode =
402             m_tree->open(path, ConfigTree::hidden);
403
404         // similar multiplexing as for SHARED_LAYOUT,
405         // with two nodes underneath
406         boost::shared_ptr<MultiplexConfigNode> mnode;
407         mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
408                                             getRegistry(),
409                                             false));
410         m_props[false] = mnode;
411         mnode->setNode(false, ConfigProperty::GLOBAL_SHARING,
412                        m_globalNode);
413         mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
414                        m_peerNode);
415         mnode->setNode(false, ConfigProperty::NO_SHARING,
416                        m_peerNode);
417         mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
418                                             getRegistry(),
419                                             true));
420         m_props[true] = mnode;
421         mnode->setNode(true, ConfigProperty::GLOBAL_SHARING,
422                        m_globalHiddenNode);
423         mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING,
424                        m_peerNode);
425         mnode->setNode(true, ConfigProperty::NO_SHARING,
426                        m_peerNode);
427         break;
428     }
429     case SHARED_LAYOUT:
430         // really use different nodes for everything
431         path = "";
432         node = m_tree->open(path, ConfigTree::visible);
433         m_globalNode.reset(new FilterConfigNode(node));
434         node = m_tree->open(path, ConfigTree::hidden);
435         m_globalHiddenNode = node;
436
437         path = m_peerPath;
438         if (path.empty()) {
439             if (!m_redirectPeerRootPath.empty()) {
440                 node.reset(new IniFileConfigNode(m_redirectPeerRootPath,
441                                                  ".internal.ini",
442                                                  false));
443                 node = m_tree->add(m_redirectPeerRootPath + "/.internal.ini",
444                                    node);
445             } else {
446                 node.reset(new DevNullConfigNode(m_contextPath + " without peer config"));
447             }
448         } else {
449             node = m_tree->open(path, ConfigTree::visible);
450         }
451         m_peerNode.reset(new FilterConfigNode(node));
452         if (path.empty()) {
453             m_hiddenPeerNode = m_peerNode;
454         } else {
455             m_hiddenPeerNode = m_tree->open(path, ConfigTree::hidden);
456         }
457
458         path = m_contextPath;
459         node = m_tree->open(path, ConfigTree::visible);
460         m_contextNode.reset(new FilterConfigNode(node));
461         m_contextHiddenNode = m_tree->open(path, ConfigTree::hidden);
462
463         // Instantiate multiplexer with the most specific node name in
464         // the set, the peer node's name. This is slightly inaccurate:
465         // error messages generated for this node in will reference
466         // the wrong config.ini file for shared properties. But
467         // there no shared properties which can trigger such an error
468         // at the moment, so this is good enough for now (MB#8037).
469         boost::shared_ptr<MultiplexConfigNode> mnode;
470         mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
471                                             getRegistry(),
472                                             false));
473         mnode->setHavePeerNodes(!m_peerPath.empty());
474         m_props[false] = mnode;
475         mnode->setNode(false, ConfigProperty::GLOBAL_SHARING,
476                        m_globalNode);
477         mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
478                        m_contextNode);
479         mnode->setNode(false, ConfigProperty::NO_SHARING,
480                        m_peerNode);
481
482         mnode.reset(new MultiplexConfigNode(m_hiddenPeerNode->getName(),
483                                             getRegistry(),
484                                             true));
485         mnode->setHavePeerNodes(!m_peerPath.empty());
486         m_props[true] = mnode;
487         mnode->setNode(true, ConfigProperty::SOURCE_SET_SHARING,
488                        m_contextHiddenNode);
489         mnode->setNode(true, ConfigProperty::NO_SHARING,
490                        m_hiddenPeerNode);
491         mnode->setNode(true, ConfigProperty::GLOBAL_SHARING,
492                        m_globalHiddenNode);
493         break;
494     }
495
496     // read version check
497     for (ConfigLevel level = CONFIG_LEVEL_ROOT;
498          level < CONFIG_LEVEL_MAX;
499          level = (ConfigLevel)(level + 1)) {
500         if (exists(level)) {
501             if (getConfigVersion(level, CONFIG_MIN_VERSION) > ConfigVersions[level][CONFIG_CUR_VERSION]) {
502                 SE_LOG_INFO(NULL, "config version check failed: %s has format %d, but this SyncEvolution release only supports format %d",
503                             ConfigLevel2String(level).c_str(),
504                             getConfigVersion(level, CONFIG_MIN_VERSION),
505                             ConfigVersions[level][CONFIG_CUR_VERSION]);
506                 // our code is too old to read the config, reject it
507                 SE_THROW_EXCEPTION_STATUS(StatusException,
508                                           StringPrintf("SyncEvolution %s is too old to read configuration '%s', please upgrade SyncEvolution.",
509                                                        VERSION, peer.c_str()),
510                                           STATUS_RELEASE_TOO_OLD);
511             }
512         }
513     }
514
515     // Note that the version check does not reject old configs because
516     // they are too old; so far, any release must be able to read any
517     // older config.
518 }
519
520 void SyncConfig::prepareConfigForWrite()
521 {
522     // check versions before bumping to something incompatible with the
523     // previous user of the config
524     for (ConfigLevel level = CONFIG_LEVEL_ROOT;
525          level < CONFIG_LEVEL_MAX;
526          level = (ConfigLevel)(level + 1)) {
527         if (getLayout() < SHARED_LAYOUT &&
528             level < CONFIG_LEVEL_PEER) {
529             // old configs do not have explicit root or context,
530             // only check peer config itself
531             continue;
532         }
533         if (exists(level)) {
534             if (getConfigVersion(level, CONFIG_CUR_VERSION) < ConfigVersions[level][CONFIG_MIN_VERSION]) {
535                 // release which created config will no longer be able to read
536                 // updated config; either alert user or migrate automatically
537                 string config;
538                 switch (level) {
539                 case CONFIG_LEVEL_CONTEXT:
540                     config = getContextName();
541                     break;
542                 case CONFIG_LEVEL_PEER:
543                     config = getConfigName();
544                     break;
545                 case CONFIG_LEVEL_ROOT:
546                 case CONFIG_LEVEL_MAX:
547                     // keep compiler happy, not reached for _MAX
548                     break;
549                 }
550                 SE_LOG_INFO(NULL, "must change format of %s '%s' in backward-incompatible way",
551                             ConfigLevel2String(level).c_str(),
552                             config.c_str());
553                 if (m_configWriteMode == MIGRATE_AUTOMATICALLY) {
554                     // migrate config and anything beneath it,
555                     // so no further checking needed
556                     migrate(config);
557                     break;
558                 } else {
559                     SE_THROW_EXCEPTION_STATUS(StatusException,
560                                               StringPrintf("Proceeding would modify config '%s' such "
561                                                            "that the previous SyncEvolution release "
562                                                            "will not be able to use it. Stopping now. "
563                                                            "Please explicitly acknowledge this step by "
564                                                            "running the following command on the command "
565                                                            "line: syncevolution --migrate '%s'",
566                                                            config.c_str(),
567                                                            config.c_str()),
568                                               STATUS_MIGRATION_NEEDED);
569                 }
570             }
571         }
572     }
573
574     // now set current versions at all levels,
575     // but without reducing versions: if a config has format
576     // "cur = 10", then properties or features added in that
577     // format remain even if the config is (temporarily?) used
578     // by a SyncEvolution binary which has "cur = 5".
579     for (ConfigLevel level = CONFIG_LEVEL_ROOT;
580          level < CONFIG_LEVEL_MAX;
581          level = (ConfigLevel)(level + 1)) {
582         if (level == CONFIG_LEVEL_PEER &&
583             m_peerPath.empty()) {
584             // no need (and no possibility) to set per-peer version)
585             break;
586         }
587         for (ConfigLimit limit = CONFIG_MIN_VERSION;
588              limit < CONFIG_VERSION_MAX;
589              limit = (ConfigLimit)(limit + 1)) {
590             // set if equal to ensure that version == 0 (the default)
591             // is set explicitly
592             if (getConfigVersion(level, limit) <= ConfigVersions[level][limit]) {
593                 setConfigVersion(level, limit, ConfigVersions[level][limit]);
594             }
595         }
596     }
597     flush();
598 }
599
600 void SyncConfig::migrate(const std::string &config)
601 {
602     if (config.empty()) {
603         // migrating root not yet supported
604         SE_THROW("internal error, migrating config root not implemented");
605     } else {
606         // migrate using the higher-level logic in the Cmdline class
607         Cmdline migrate(m_peer.c_str(),
608                         "--migrate",
609                         config.c_str(),
610                         NULL);
611         bool res = migrate.parse() && migrate.run();
612         if (!res) {
613             SE_THROW(StringPrintf("migration of config '%s' failed", config.c_str()));
614         }
615
616         // files that our tree access may have changed, refresh our
617         // in-memory copy
618         m_tree->reload();
619     }
620 }
621
622 string SyncConfig::getRootPath() const
623 {
624     return m_tree->getRootPath();
625 }
626
627 void SyncConfig::addPeers(const string &root,
628                           const std::string &configname,
629                           SyncConfig::ConfigList &res) {
630     FileConfigTree tree(root, "", SyncConfig::HTTP_SERVER_LAYOUT);
631     list<string> servers = tree.getChildren("");
632     BOOST_FOREACH(const string &server, servers) {
633         // sanity check: only list server directories which actually
634         // contain a configuration. To distinguish between a context
635         // (~/.config/syncevolution/default) and an HTTP server config
636         // (~/.config/syncevolution/scheduleworld), we check for the
637         // "peer" subdirectory that is only in the former.
638         //
639         // Contexts which don't have a peer are therefore incorrectly
640         // listed as a peer. Short of adding a special hidden file
641         // this can't be fixed. This is probably overkill and thus not
642         // done yet.
643         string peerPath = server + "/peers";
644         if (!access((root + "/" + peerPath).c_str(), F_OK)) {
645             // not a real HTTP server, search for peers
646             BOOST_FOREACH(const string &peer, tree.getChildren(peerPath)) {
647                 res.push_back(pair<string, string> (normalizeConfigString(peer + "@" + server),
648                                                   root + "/" + peerPath + "/" + peer));
649             }
650         } else if (!access((root + "/" + server + "/" + configname).c_str(), F_OK)) {
651             res.push_back(pair<string, string> (server, root + "/" + server));
652         }
653     }
654 }
655
656 /** returns true if a precedes b (strict weak ordering) */
657 static bool cmpConfigEntries(const StringPair &a, const StringPair &b)
658 {
659     string peerA, contextA, peerB, contextB;
660     SyncConfig::splitConfigString(a.first, peerA, contextA);
661     SyncConfig::splitConfigString(b.first, peerB, contextB);
662     int res;
663     res = contextA.compare(contextB);
664     if (res == 0) {
665         res = peerA.compare(peerB);
666         if (res == 0) {
667             res = a.second.compare(b.second);
668         }
669     }
670     return res < 0;
671 }
672
673 SyncConfig::ConfigList SyncConfig::getConfigs()
674 {
675     ConfigList res;
676
677     addPeers(getOldRoot(), "config.txt", res);
678     addPeers(getNewRoot(), "config.ini", res);
679
680     // Sort the list by (context, peer name, path);
681     // better than returning it in random order.
682     // This sort order (compared to simple lexical
683     // sorting based on the full config name) has
684     // the advantage that peer names or contexts with
685     // suffix (foo.old vs. foo) come later.
686     res.sort(cmpConfigEntries);
687
688     return res;
689 }
690
691 static string SyncEvolutionTemplateDir()
692 {
693     string templateDir(TEMPLATE_DIR);
694     const char *envvar = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
695     if (envvar) {
696         templateDir = envvar;
697     }
698     return templateDir;
699 }
700
701 SyncConfig::TemplateList SyncConfig::matchPeerTemplates(const DeviceList &peers, bool fuzzyMatch)
702 {
703     TemplateList result;
704     // match against all possible templates without any assumption on directory
705     // layout, the match is entirely based on the metadata template.ini
706     string templateDir(SyncEvolutionTemplateDir());
707     std::queue <std::string, std::list<std::string> > directories;
708
709     directories.push(templateDir);
710     templateDir = SubstEnvironment("${XDG_CONFIG_HOME}/syncevolution-templates");
711     directories.push(templateDir);
712     while (!directories.empty()) {
713         string sDir = directories.front();
714         directories.pop();
715         if (isDir(sDir)) {
716             // check all sub directories
717             ReadDir dir(sDir);
718             BOOST_FOREACH(const string &entry, dir) {
719                 // ignore hidden files, . and ..
720                 if (!boost::starts_with(entry, ".")) {
721                     directories.push(sDir + "/" + entry);
722                 }
723             }
724         } else {
725             TemplateConfig templateConf (sDir);
726             if (boost::ends_with(sDir, "~") ||
727                 !templateConf.isTemplateConfig()) {
728                 // ignore temporary files and files which do
729                 // not contain a valid template
730                 continue;
731             }
732             BOOST_FOREACH (const DeviceList::value_type &entry, peers){
733                 std::string fingerprint(entry.getFingerprint());
734                 // peerName should be empty if no reliable device info is on hand.
735                 std::string peerName = entry.m_pnpInformation ? fingerprint : "";
736
737                 int rank = templateConf.metaMatch (entry.getFingerprint(), entry.m_matchMode);
738                 if (fuzzyMatch){
739                     if (rank > TemplateConfig::NO_MATCH) {
740                         result.push_back (boost::shared_ptr<TemplateDescription>(
741                                     new TemplateDescription(templateConf.getTemplateId(),
742                                                             templateConf.getDescription(),
743                                                             rank,
744                                                             peerName,
745                                                             entry.m_deviceId,
746                                                             entry.m_deviceName,
747                                                             sDir,
748                                                             templateConf.getFingerprint(),
749                                                             templateConf.getTemplateName()
750                                                             )
751                                     ));
752                     }
753                 } else if (rank == TemplateConfig::BEST_MATCH){
754                     result.push_back (boost::shared_ptr<TemplateDescription>(
755                                 new TemplateDescription(templateConf.getTemplateId(),
756                                                         templateConf.getDescription(),
757                                                         rank,
758                                                         peerName,
759                                                         entry.m_deviceId,
760                                                         entry.m_deviceName,
761                                                         sDir,
762                                                         templateConf.getFingerprint(),
763                                                         templateConf.getTemplateName())
764                                 ));
765                     break;
766                 }
767             }
768         }
769     }
770
771     result.sort (TemplateDescription::compare_op);
772     return result;
773 }
774
775
776 boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &server)
777 {
778     if (server.empty()) {
779         // Empty template name => no such template. This check is
780         // necessary because otherwise we end up with SyncConfig(""),
781         // which is a configuration where peer-specific properties
782         // cannot be set, triggering an errror in config->setDevID().
783         return boost::shared_ptr<SyncConfig>();
784     }
785
786     // case insensitive search for read-only file template config
787     string templateConfig(SyncEvolutionTemplateDir());
788
789     // before starting another fuzzy match process, first try to load the
790     // template directly taking the parameter as the path
791     if (server == "none") {
792         // nothing to read from, just set some defaults below
793     } else if (TemplateConfig::isTemplateConfig(server)) {
794         templateConfig = server;
795     } else {
796         SyncConfig::DeviceList devices;
797         devices.push_back (DeviceDescription("", server, MATCH_ALL));
798         templateConfig = "";
799         TemplateList templates = matchPeerTemplates (devices, false);
800         if (!templates.empty()) {
801             templateConfig = templates.front()->m_path;
802         }
803         if (templateConfig.empty()) {
804             // return "not found"
805             return boost::shared_ptr<SyncConfig>();
806         }
807     }
808     
809     boost::shared_ptr<ConfigTree> tree(new SingleFileConfigTree(templateConfig));
810     boost::shared_ptr<SyncConfig> config(new SyncConfig(server, tree));
811     boost::shared_ptr<PersistentSyncSourceConfig> source;
812
813     config->setDefaults(false);
814     config->setDevID(string("syncevolution-") + UUID());
815
816     // leave the rest empty for special "none" template
817     if (server == "none") {
818         return config;
819     }
820
821     // check for icon
822     if (config->getIconURI().empty()) {
823         string dirname, filename;
824         splitPath(templateConfig, dirname, filename);
825         ReadDir dir(getDirname(dirname));
826
827         // remove last suffix, regardless what it is
828         size_t pos = filename.rfind('.');
829         if (pos != filename.npos) {
830             filename.resize(pos);
831         }
832         filename += "-icon";
833
834         BOOST_FOREACH(const string &entry, dir) {
835             if (boost::istarts_with(entry, filename)) {
836                 config->setIconURI("file://" + dirname + "/" + entry);
837                 break;
838             }
839         }
840     }
841
842     // "default" maps to SyncEvolution server template, which is not
843     // consumer ready. When used as "default" by the GTK sync UI,
844     // the UI expects the "consumer ready" flag to be set. Do that
845     // here. Also unset the peer name, because otherwise it shows
846     // up in the UI.
847     if (server == "default") {
848         config->setConsumerReady(true);
849         config->setUserPeerName(InitStateString());
850     }
851
852     return config;
853 }
854
855 bool SyncConfig::exists() const
856 {
857     return m_peerPath.empty() ?
858         m_contextNode->exists() :
859         m_peerNode->exists();
860 }
861
862 bool SyncConfig::exists(ConfigLevel level) const
863 {
864     switch (level) {
865     case CONFIG_LEVEL_ROOT:
866         return m_globalNode->exists();
867         break;
868     case CONFIG_LEVEL_CONTEXT:
869         return m_contextNode->exists();
870         break;
871     case CONFIG_LEVEL_PEER:
872         return m_peerNode->exists();
873         break;
874     default:
875         return false;
876     }
877 }
878
879 string SyncConfig::getContextName() const
880 {
881     string peer, context;
882     splitConfigString(getConfigName(), peer, context);
883     return string("@") + context;
884 }
885
886 string SyncConfig::getPeerName() const
887 {
888     string peer, context;
889     splitConfigString(getConfigName(), peer, context);
890     return peer;
891 }
892
893 list<string> SyncConfig::getPeers() const
894 {
895     list<string> res;
896
897     if (!hasPeerProperties()) {
898         FileConfigTree tree(getRootPath(), "", SHARED_LAYOUT);
899         res = tree.getChildren("peers");
900     }
901
902     return res;
903 }
904
905 void SyncConfig::preFlush(UserInterface &ui)
906 {
907     /* Iterator over all sync global and source properties 
908      * one by one and check whether they need to save password */
909
910     /* save password in the global config node */
911     ConfigPropertyRegistry& registry = getRegistry();
912     BOOST_FOREACH(const ConfigProperty *prop, registry) {
913         prop->savePassword(ui, m_peer, *getProperties());
914     }
915
916     /** grep each source and save their password */
917     list<string> configuredSources = getSyncSources();
918     BOOST_FOREACH(const string &sourceName, configuredSources) {
919         //boost::shared_ptr<SyncSourceConfig> sc = getSyncSourceConfig(sourceName);
920         ConfigPropertyRegistry& registry = SyncSourceConfig::getRegistry();
921         SyncSourceNodes sourceNodes = getSyncSourceNodes(sourceName);
922
923         BOOST_FOREACH(const ConfigProperty *prop, registry) {
924             prop->savePassword(ui, m_peer, *getProperties(),
925                                sourceName, sourceNodes.getProperties());
926         }
927     }
928 }
929
930 void SyncConfig::flush()
931 {
932     if (!isEphemeral()) {
933         m_tree->flush();
934     }
935 }
936
937 void SyncConfig::remove()
938 {
939     boost::shared_ptr<ConfigTree> tree = m_tree;
940
941     // stop using the config nodes, they might get removed now
942     makeVolatile();
943
944     tree->remove(m_peerPath.empty() ?
945                  m_contextPath :
946                  m_peerPath);
947 }
948
949 boost::shared_ptr<PersistentSyncSourceConfig> SyncConfig::getSyncSourceConfig(const string &name)
950 {
951     SyncSourceNodes nodes = getSyncSourceNodes(name);
952     return boost::shared_ptr<PersistentSyncSourceConfig>(new PersistentSyncSourceConfig(name, nodes));
953 }
954
955 list<string> SyncConfig::getSyncSources() const
956 {
957     // Return *all* sources configured in this context,
958     // not just those configured for the peer. This
959     // is necessary so that sources created for some other peer
960     // show up for the current one, to prevent overwriting
961     // existing properties unintentionally.
962     // Returned sources are an union of:
963     // 1. contextpath/sources
964     // 2. peers/[one-peer]/sources
965     // 3. sources in source filter
966     set<string> sources;
967     list<string> sourceList;
968     if (m_layout == SHARED_LAYOUT) {
969         // get sources in context
970         sourceList = m_tree->getChildren(m_contextPath + "/sources");
971         sources.insert(sourceList.begin(), sourceList.end());
972         // get sources from peer if it's not empty and merge into
973         // full set of sources
974         if (!m_peerPath.empty()) {
975             sourceList = m_tree->getChildren(m_peerPath + "/sources");
976             sources.insert(sourceList.begin(), sourceList.end());
977         }
978     } else {
979         // get sources from peer
980         sourceList = m_tree->getChildren(m_peerPath +
981                                          (m_layout == SYNC4J_LAYOUT ? 
982                                           "/spds/sources" :
983                                           "/sources"));
984         sources.insert(sourceList.begin(), sourceList.end());
985     }
986     // get sources from filter and union them into returned sources
987     BOOST_FOREACH(const SourceProps::value_type &value, m_sourceFilters) {
988         if (value.first.empty()) {
989             // ignore filter for all sources
990             continue;
991         }
992         sources.insert(value.first);
993     }
994
995     // Convert back to simple list. As a nice side-effect of
996     // temporarily using a set, the final list is sorted.
997     return list<string>(sources.begin(), sources.end());
998 }
999
1000 SyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
1001                                                const string &changeId)
1002 {
1003     if (m_nodeCache.find(name) != m_nodeCache.end()) {
1004         // reuse existing set of nodes
1005         return m_nodeCache[name];
1006     }
1007
1008     /** shared source properties */
1009     boost::shared_ptr<FilterConfigNode> sharedNode;
1010     /** per-peer source properties */
1011     boost::shared_ptr<FilterConfigNode> peerNode;
1012     /** per-peer internal properties and meta data */
1013     boost::shared_ptr<ConfigNode> hiddenPeerNode,
1014         serverNode,
1015         trackingNode;
1016     string cacheDir;
1017
1018     // store configs lower case even if the UI uses mixed case
1019     string lower = name;
1020     boost::to_lower(lower);
1021
1022     boost::shared_ptr<ConfigNode> node;
1023     string sharedPath, peerPath;
1024     switch (m_layout) {
1025     case SYNC4J_LAYOUT:
1026         peerPath = m_peerPath + "/spds/sources/" + lower;
1027         break;
1028     case HTTP_SERVER_LAYOUT:
1029         peerPath = m_peerPath + "/sources/" + lower;
1030         break;
1031     case SHARED_LAYOUT:
1032         if (!m_peerPath.empty()) {
1033             peerPath = m_peerPath + "/sources/" + lower;
1034         }
1035         sharedPath = m_contextPath + string("/sources/") + lower;
1036         break;
1037     }
1038
1039     // Compatibility mode for reading configs which have "type" instead
1040     // of "backend/databaseFormat/syncFormat/forceSyncFormat": determine
1041     // the new values based on the old property, then inject the new
1042     // values into the SyncSourceNodes by adding an intermediate layer
1043     // of FilterConfigNodes. The top FilterConfigNode layer is the one
1044     // which might get modified, the one underneath remains hidden and
1045     // thus preserves the new values even if the caller does a setFilter().
1046     bool compatMode = getConfigVersion(CONFIG_LEVEL_CONTEXT, CONFIG_CUR_VERSION) < 1;
1047     SourceType sourceType;
1048     if (compatMode) {
1049         node = m_tree->open(peerPath.empty() ? sharedPath : peerPath, ConfigTree::visible);
1050         string type;
1051         if (node->getProperty("type", type)) {
1052             sourceType = SourceType(type);
1053         } else {
1054             // not set: avoid compatibility mode
1055             compatMode = false;
1056         }
1057     }
1058
1059     if (peerPath.empty()) {
1060         node.reset(new DevNullConfigNode(m_contextPath + " without peer configuration"));
1061         peerNode.reset(new FilterConfigNode(node));
1062         hiddenPeerNode =
1063             trackingNode =
1064             serverNode = node;
1065     } else {
1066         // Here we assume that m_tree is a FileConfigTree. Otherwise getRootPath()
1067         // will not point into a normal file system.
1068         cacheDir = m_tree->getRootPath() + "/" + peerPath + "/.cache";
1069
1070         node = m_tree->open(peerPath, ConfigTree::visible);
1071         if (compatMode) {
1072             boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
1073             compat->addFilter("syncFormat",
1074                               InitStateString(sourceType.m_format, !sourceType.m_format.empty()));
1075             compat->addFilter("forceSyncFormat",
1076                               sourceType.m_forceFormat ?
1077                               InitStateString("1", true) :
1078                               InitStateString("0", false));
1079             if (sharedPath.empty()) {
1080                 compat->addFilter("databaseFormat",
1081                                   InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty()));
1082                 compat->addFilter("backend",
1083                                   InitStateString(sourceType.m_backend, !sourceType.m_backend.empty()));
1084             }
1085             node = compat;
1086         }
1087         peerNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
1088         hiddenPeerNode = m_tree->open(peerPath, ConfigTree::hidden);
1089         trackingNode = m_tree->open(peerPath, ConfigTree::other, changeId);
1090         serverNode = m_tree->open(peerPath, ConfigTree::server, changeId);
1091     }
1092
1093     if (isEphemeral()) {
1094         // Throw away meta data.
1095         trackingNode.reset(new VolatileConfigNode);
1096         hiddenPeerNode.reset(new VolatileConfigNode);
1097         serverNode.reset(new VolatileConfigNode);
1098     } else if (!m_redirectPeerRootPath.empty()) {
1099         // Local sync: overwrite per-peer nodes with nodes inside the
1100         // parents tree. Otherwise different configs syncing locally
1101         // against the same context end up sharing .internal.ini and
1102         // .other.ini files inside that context.
1103         string path = m_redirectPeerRootPath + "/sources/" + lower;
1104         trackingNode.reset(new IniHashConfigNode(path,
1105                                                  ".other.ini",
1106                                                  false));
1107         trackingNode = m_tree->add(path + "/.other.ini", trackingNode);
1108         boost::shared_ptr<ConfigNode> node(new IniHashConfigNode(path,
1109                                                                  ".internal.ini",
1110                                                                  false));
1111         hiddenPeerNode.reset(new FilterConfigNode(node));
1112         hiddenPeerNode = boost::static_pointer_cast<FilterConfigNode>(m_tree->add(path + "/.internal.ini", peerNode));
1113         if (peerPath.empty()) {
1114             hiddenPeerNode = peerNode;
1115         }
1116     }
1117
1118     if (sharedPath.empty()) {
1119         sharedNode = peerNode;
1120     } else {
1121         node = m_tree->open(sharedPath, ConfigTree::visible);
1122         if (compatMode) {
1123             boost::shared_ptr<FilterConfigNode> compat(new FilterConfigNode(node));
1124             compat->addFilter("databaseFormat",
1125                               InitStateString(sourceType.m_localFormat, !sourceType.m_localFormat.empty()));
1126             compat->addFilter("backend",
1127                               InitStateString(sourceType.m_backend, !sourceType.m_backend.empty()));
1128             node = compat;
1129         }
1130         sharedNode.reset(new FilterConfigNode(node, m_sourceFilters.createSourceFilter(name)));
1131     }
1132
1133     SyncSourceNodes nodes(!peerPath.empty(), sharedNode, peerNode, hiddenPeerNode, trackingNode, serverNode, cacheDir);
1134     m_nodeCache.insert(make_pair(name, nodes));
1135     return nodes;
1136 }
1137
1138 ConstSyncSourceNodes SyncConfig::getSyncSourceNodes(const string &name,
1139                                                     const string &changeId) const
1140 {
1141     return const_cast<SyncConfig *>(this)->getSyncSourceNodes(name, changeId);
1142 }
1143
1144 SyncSourceNodes SyncConfig::getSyncSourceNodesNoTracking(const string &name)
1145 {
1146     SyncSourceNodes nodes = getSyncSourceNodes(name);
1147     boost::shared_ptr<ConfigNode> dummy(new VolatileConfigNode());
1148     return SyncSourceNodes(nodes.m_havePeerNode,
1149                            nodes.m_sharedNode,
1150                            nodes.m_peerNode,
1151                            nodes.m_hiddenPeerNode,
1152                            dummy,
1153                            nodes.m_serverNode,
1154                            nodes.m_cacheDir);
1155 }
1156
1157 static ConfigProperty syncPropSyncURL("syncURL",
1158                                       "Identifies how to contact the peer,\n"
1159                                       "best explained with some examples.\n\n"
1160                                       "HTTP(S) SyncML servers::\n\n"
1161                                       "  http://my.funambol.com/sync\n"
1162                                       "  http://sync.scheduleworld.com/funambol/ds\n"
1163                                       "  https://m.google.com/syncml\n\n"
1164                                       "OBEX over Bluetooth uses the MAC address, with\n"
1165                                       "the channel chosen automatically::\n\n"
1166                                       "  obex-bt://00:0A:94:03:F3:7E\n\n"
1167                                       "If the automatism fails, the channel can also be specified::\n\n"
1168                                       "  obex-bt://00:0A:94:03:F3:7E+16\n\n"
1169                                       "For peers contacting us via Bluetooth, the MAC address is\n"
1170                                       "used to identify it before the sync starts. Multiple\n"
1171                                       "urls can be specified in one syncURL property::\n\n"
1172                                       "  obex-bt://00:0A:94:03:F3:7E obex-bt://00:01:02:03:04:05\n\n"
1173                                       "In the future this might be used to contact the peer\n"
1174                                       "via one of several transports; right now, only the first\n"
1175                                       "one is tried." // MB #9446
1176                                       );
1177
1178 static ConfigProperty syncPropDevID("deviceId",
1179                                     "The SyncML server gets this string and will use it to keep track of\n"
1180                                     "changes that still need to be synchronized with this particular\n"
1181                                     "client; it must be set to something unique (like the pseudo-random\n"
1182                                     "string created automatically for new configurations) among all clients\n"
1183                                     "accessing the same server.\n"
1184                                     "myFUNAMBOL also requires that the string starts with sc-pim-");
1185 static ConfigProperty syncPropUsername("username",
1186                                        "user name used for authorization with the SyncML server",
1187                                        "");
1188 static PasswordConfigProperty syncPropPassword("password",
1189                                                "password used for authorization with the peer;\n"
1190                                                "in addition to specifying it directly as plain text, it can\n"
1191                                                "also be read from the standard input or from an environment\n"
1192                                                "variable of your choice::\n\n"
1193                                                "  plain text  : password = <insert your password here>\n"
1194                                                "  ask         : password = -\n"
1195                                                "  env variable: password = ${<name of environment variable>}\n");
1196 static BoolConfigProperty syncPropPreventSlowSync("preventSlowSync",
1197                                                   "During a slow sync, the SyncML server must match all items\n"
1198                                                   "of the client with its own items and detect which ones it\n"
1199                                                   "already has based on properties of the items. This is slow\n"
1200                                                   "(client must send all its data) and can lead to duplicates\n"
1201                                                   "(when the server fails to match correctly).\n"
1202                                                   "It is therefore sometimes desirable to wipe out data on one\n"
1203                                                   "side with a refresh-from-client/server sync instead of doing\n"
1204                                                   "a slow sync.\n"
1205                                                   "When this option is enabled, slow syncs that could cause problems\n"
1206                                                   "are not allowed to proceed. Instead, the affected sources are\n"
1207                                                   "skipped, allowing the user to choose a suitable sync mode in\n"
1208                                                   "the next run (slow sync selected explicitly, refresh sync).\n"
1209                                                   "The following situations are handled:\n\n"
1210                                                   "- running as client with no local data => unproblematic,\n"
1211                                                   "  slow sync is allowed to proceed automatically\n"
1212                                                   "- running as client with local data => client has no\n"
1213                                                   "  information about server, so slow sync might be problematic\n"
1214                                                   "  and is prevented\n"
1215                                                   "- client has data, server asks for slow sync because all its data\n"
1216                                                   "  was deleted (done by Memotoo and Mobical, because they treat\n"
1217                                                   "  this as 'user wants to start from scratch') => the sync would\n"
1218                                                   "  recreate all the client's data, even if the user really wanted\n"
1219                                                   "  to have it deleted, therefore slow sync is prevented\n",
1220                                                   "TRUE");
1221 static BoolConfigProperty syncPropUseProxy("useProxy",
1222                                            "set to T to choose an HTTP proxy explicitly; otherwise the default\n"
1223                                            "proxy settings of the underlying HTTP transport mechanism are used;\n"
1224                                            "only relevant when contacting the peer via HTTP",
1225                                            "FALSE");
1226 static ConfigProperty syncPropProxyHost("proxyHost",
1227                                         "proxy URL (``http://<host>:<port>``)");
1228 static ConfigProperty syncPropProxyUsername("proxyUsername",
1229                                             "authentication for proxy: username");
1230 static ProxyPasswordConfigProperty syncPropProxyPassword("proxyPassword",
1231                                                          "proxy password, can be specified in different ways,\n"
1232                                                          "see SyncML server password for details\n",
1233                                                          "",
1234                                                          "proxy");
1235 static StringConfigProperty syncPropClientAuthType("clientAuthType",
1236                                                    "- empty or \"md5\" for secure method (recommended)\n"
1237                                                    "- \"basic\" for insecure method\n"
1238                                                    "\n"
1239                                                    "This setting is only for debugging purpose and only\n"
1240                                                    "has an effect during the initial sync of a client.\n"
1241                                                    "Later it remembers the method that was supported by\n"
1242                                                    "the server and uses that. When acting as server,\n"
1243                                                    "clients contacting us can use both basic and md5\n"
1244                                                    "authentication.\n",
1245                                                    "md5",
1246                                                    "",
1247                                                    Values() +
1248                                                    (Aliases("basic") + "syncml:auth-basic") +
1249                                                    (Aliases("md5") + "syncml:auth-md5" + ""));
1250 static ULongConfigProperty syncPropMaxMsgSize("maxMsgSize",
1251                                               "The maximum size of each message can be set (maxMsgSize) and the\n"
1252                                               "peer can be told to never sent items larger than a certain\n"
1253                                               "threshold (maxObjSize). Presumably the peer has to truncate or\n"
1254                                               "skip larger items. Sizes are specified as number of bytes.",
1255                                               "150000");
1256 static UIntConfigProperty syncPropMaxObjSize("maxObjSize", "", "4000000");
1257
1258 static BoolConfigProperty syncPropWBXML("enableWBXML",
1259                                         "use the more compact binary XML (WBXML) for messages between client and server;\n"
1260                                         "not applicable when the peer is a SyncML client, because then the client\n"
1261                                         "chooses the encoding",
1262                                         "TRUE");
1263 static BoolConfigProperty syncPropRefreshSync("enableRefreshSync",
1264                                               "Use the more advanced refresh-from-server sync mode to\n"
1265                                               "implement the refresh-from-remote operation. Some SyncML\n"
1266                                               "servers do not support this. Therefore the default is to\n"
1267                                               "delete local data before doing a slow sync, which has the\n"
1268                                               "same effect. However, some servers work better when they\n"
1269                                               "are told explicitly that the sync is a refresh sync. For\n"
1270                                               "example, Funambol's One Media server rejects too many slow\n"
1271                                               "syncs in a row with a 417 'retry later' error.\n",
1272                                               "FALSE");
1273 static ConfigProperty syncPropLogDir("logdir",
1274                                      "full path to directory where automatic backups and logs\n"
1275                                      "are stored for all synchronizations; if unset, then\n"
1276                                      "\"${XDG_CACHE_HOME}/syncevolution/<server>\" (which\n"
1277                                      "usually expands to ${HOME}/.cache/...) will be used;\n"
1278                                      "if \"none\", then no backups of the databases are made and any\n"
1279                                      "output is printed directly to the screen");
1280 static UIntConfigProperty syncPropMaxLogDirs("maxlogdirs",
1281                                              "Controls how many session directories are kept at most in the logdir.\n"
1282                                              "Unless set to zero, SyncEvolution will remove old directories and\n"
1283                                              "all their content to prevent the number of log directories from\n"
1284                                              "growing beyond the given limit. It tries to be intelligent and will\n"
1285                                              "remove sessions in which nothing interesting happened (no errors,\n"
1286                                              "no data changes) in favor of keeping sessions where something\n"
1287                                              "happened, even if those sessions are older.",
1288                                              "10");
1289 static UIntConfigProperty syncPropLogLevel("loglevel",
1290                                           "level of detail for log messages:\n"
1291                                           "- 0 (or unset) = INFO messages without log file, DEBUG with log file\n"
1292                                           "- 1 = only ERROR messages\n"
1293                                           "- 2 = also INFO messages\n"
1294                                           "- 3 = also DEBUG messages\n"
1295                                           "> 3 = increasing amounts of debug messages for developers");
1296 static UIntConfigProperty syncPropNotifyLevel("notifyLevel",
1297                                               "Level of detail for desktop notifications. Currently such\n"
1298                                               "notifications are generated only for automatically started\n"
1299                                               "sync sessions.\n"
1300                                               "\n"
1301                                               "0 - suppress all notifications\n"
1302                                               "1 - show only errors\n"
1303                                               "2 - show information about changes and errors (in practice currently the same as level 3)\n"
1304                                               "3 - show all notifications, including starting a sync\n",
1305                                               "3");
1306 static BoolConfigProperty syncPropPrintChanges("printChanges",
1307                                                "enables or disables the detailed (and sometimes slow) comparison\n"
1308                                                "of database content before and after a sync session",
1309                                                "TRUE");
1310 static BoolConfigProperty syncPropDumpData("dumpData",
1311                                            "enables or disables the automatic backup of database content\n"
1312                                            "before and after a sync session (always enabled if printChanges is enabled)",
1313                                            "TRUE");
1314 static SecondsConfigProperty syncPropRetryDuration("RetryDuration",
1315                                           "The total amount of time in seconds in which the SyncML\n"
1316                                           "client tries to get a response from the server.\n"
1317                                           "During this time, the client will resend messages\n"
1318                                           "in regular intervals (RetryInterval) if no response\n"
1319                                           "is received or the message could not be delivered due\n"
1320                                           "to transport problems. When this time is exceeded\n"
1321                                           "without a response, the synchronization aborts without\n"
1322                                           "sending further messages to the server.\n"
1323                                           "\n"
1324                                           "When acting as server, this setting controls how long\n"
1325                                           "a client is allowed to not send a message before the\n"
1326                                           "synchronization is aborted."
1327                                           ,"5M");
1328 static SecondsConfigProperty syncPropRetryInterval("RetryInterval",
1329                                           "The number of seconds between the start of SyncML message sending\n"
1330                                           "and the start of the retransmission. If the interval has\n"
1331                                           "already passed when a message send returns, the\n"
1332                                           "message is resent immediately. Resending without\n"
1333                                           "any delay will never succeed and therefore specifying 0\n"
1334                                           "disables retries.\n"
1335                                           "\n"
1336                                           "Servers cannot resend messages, so this setting has no\n"
1337                                           "effect in that case.\n"
1338                                           "\n"
1339                                           "The WebDAV backend also resends messages after a temporary\n"
1340                                           "network error. It uses exponential backoff to determine when\n"
1341                                           "the server is available again. This setting is divided by 24\n"
1342                                           "to obtain the initial delay (default: 2m => 5s), which is then\n"
1343                                           "doubled for each retry."
1344                                           ,"2M");
1345 static BoolConfigProperty syncPropPeerIsClient("PeerIsClient",
1346                                           "Indicates whether this configuration is about a\n"
1347                                           "client peer or server peer.\n",
1348                                           "FALSE");
1349 static SafeConfigProperty syncPropPeerName("PeerName",
1350                                            "An arbitrary name for the peer referenced by this config.\n"
1351                                            "Might be used by a GUI. The command line tool always uses the\n"
1352                                            "the configuration name.");
1353 static ConfigProperty syncPropSyncMLVersion("SyncMLVersion",
1354                                             "On a client, the latest commonly supported SyncML version\n"
1355                                             "is used when contacting a server. One of '1.0/1.1/1.2' can\n"
1356                                             "be used to pick a specific version explicitly.\n"
1357                                             "\n"
1358                                             "On a server, this option controls what kind of Server Alerted\n"
1359                                             "Notification is sent to the client to start a synchronization.\n"
1360                                             "By default, first the format from 1.2 is tried, then in case\n"
1361                                             "of failure, the older one from 1.1. 1.2/1.1 can be set\n"
1362                                             "explicitly, which disables the automatism.\n"
1363                                             "\n"
1364                                             "Instead or in adddition to the version, several keywords can\n"
1365                                             "be set in this property (separated by spaces or commas):\n"
1366                                             "\n"
1367                                             "- NOCTCAP - avoid sending CtCap meta information\n"
1368                                             "- NORESTART - disable the sync mode extension that SyncEvolution\n"
1369                                             "  client and server use to negotiate whether both sides support\n"
1370                                             "  running multiple sync iterations in the same session\n"
1371                                             "- REQUESTMAXTIME=<time> - override the rate at which the\n"
1372                                             "  SyncML server sends preliminary replies while preparing\n"
1373                                             "  local storages in the background. This helps to avoid timeouts\n"
1374                                             "  in the SyncML client. Depends on multithreading.\n"
1375 #ifdef HAVE_THREAD_SUPPORT
1376                                             // test-dbus.py checks for 'is thread-safe'!
1377                                             "  This SyncEvolution binary is thread-safe and thus this feature\n"
1378                                             "  is enabled by default for HTTP servers, with a delay of 2 minutes\n"
1379                                             "  between messages. Other servers (Bluetooth, local sync) should not\n"
1380                                             "  need preliminary replies and the feature is disabled, although\n"
1381                                             "  it can be enabled by setting the time explicitly.\n"
1382 #else
1383                                             "  This SyncEvolution binary is not thread-safe and thus this feature\n"
1384                                             "  is disabled by default, although it can be enabled if absolutely\n"
1385                                             "  needed by setting the time explicitly.\n"
1386 #endif
1387                                             "  <time> can be specified like other durations in the config,\n"
1388                                             "  for example as REQUESTMAXTIME=2m.\n"
1389                                             "\n"
1390                                             "Setting these flags should only be necessary as workaround for\n"
1391                                             "broken peers.\n"
1392                                             );
1393
1394 static ConfigProperty syncPropRemoteIdentifier("remoteIdentifier",
1395                                       "the identifier sent to the remote peer for a server initiated sync.\n"
1396                                       "if not set, deviceId will be used instead\n",
1397                                       "");
1398 static ConfigProperty syncPropSSLServerCertificates("SSLServerCertificates",
1399                                                     "A string specifying the location of the certificates\n"
1400                                                     "used to authenticate the server. When empty, the\n"
1401                                                     "system's default location will be searched.\n"
1402                                                     "\n"
1403                                                     "SSL support when acting as HTTP server is implemented\n"
1404                                                     "by the HTTP server frontend, not with these properties.",
1405                                                     SYNCEVOLUTION_SSL_SERVER_CERTIFICATES);
1406 static BoolConfigProperty syncPropSSLVerifyServer("SSLVerifyServer",
1407                                                   "The client refuses to establish the connection unless\n"
1408                                                   "the server presents a valid certificate. Disabling this\n"
1409                                                   "option considerably reduces the security of SSL\n"
1410                                                   "(man-in-the-middle attacks become possible) and is not\n"
1411                                                   "recommended.\n",
1412                                                   "TRUE");
1413 static BoolConfigProperty syncPropSSLVerifyHost("SSLVerifyHost",
1414                                                 "The client refuses to establish the connection unless the\n"
1415                                                 "server's certificate matches its host name. In cases where\n"
1416                                                 "the certificate still seems to be valid it might make sense\n"
1417                                                 "to disable this option and allow such connections.\n",
1418                                                 "TRUE");
1419
1420 static ConfigProperty syncPropWebURL("WebURL",
1421                                      "The URL of a web page with further information about the server.\n"
1422                                      "Used only by the GUI."
1423                                      "");
1424
1425 static ConfigProperty syncPropIconURI("IconURI",
1426                                       "The URI of an icon representing the server graphically.\n"
1427                                       "Should be a 48x48 pixmap or a SVG (preferred).\n"
1428                                       "Used only by the GUI.");
1429
1430 static BoolConfigProperty syncPropConsumerReady("ConsumerReady",
1431                                                 "Set to true in a configuration template to indicate\n"
1432                                                 "that the server works well enough and is available\n"
1433                                                 "for normal users. Used by the GUI to limit the choice\n"
1434                                                 "of configurations offered to users.\n"
1435                                                 "Has no effect in a user's server configuration.\n",
1436                                                 "FALSE");
1437
1438 /**
1439  * Some guidelines for peerType = WebDAV:
1440  * - Such templates may only be used to create the 'target-config@<target>.
1441  *   configurations. Typically <target> can be the same as the template's
1442  *   name.
1443  * - Because determining the default database in WebDAV can be difficult,
1444  *   the GUI should allow the user to choose and set the "uri"
1445  *   properties accordingly.
1446  * - A GUI should also create a <target> configuration for synchronizing
1447  *   against the WebDAV backends.
1448  */
1449 static ConfigProperty syncPropPeerType("peerType",
1450                                        "Defines what a configuration is meant to be used for.\n"
1451                                        "Used in templates and the resulting configs to tell a GUI\n"
1452                                        "that special handling may be necessary. GUIs should ignore\n"
1453                                        "unknown types.\n"
1454                                        "The traditional SyncML configs use an empty value.\n"
1455                                        "\"WebDAV\" is used for the WebDAV side in a local synchronization.\n");
1456
1457 static ULongConfigProperty syncPropHashCode("HashCode", "used by the SyncML library internally; do not modify");
1458
1459 static ConfigProperty syncPropConfigDate("ConfigDate", "used by the SyncML library internally; do not modify");
1460
1461 static SafeConfigProperty syncPropRemoteDevID("remoteDeviceId",
1462                                               "SyncML ID of our peer, empty if unknown; must be set only when\n"
1463                                               "the peer is a SyncML client contacting us via HTTP.\n"
1464                                               "Clients contacting us via OBEX/Bluetooth can be identified\n"
1465                                               "either via this remoteDeviceId property or by their MAC\n"
1466                                               "address, if that was set in the syncURL property.\n"
1467                                               "\n"
1468                                               "If this property is empty and the peer synchronizes with\n"
1469                                               "this configuration chosen by some other means, then its ID\n"
1470                                               "is recorded here automatically and later used to verify that\n"
1471                                               "the configuration is not accidentally used by a different\n"
1472                                               "peer.");
1473
1474 static SafeConfigProperty syncPropNonce("lastNonce",
1475                                         "MD5 nonce of our peer, empty if not set yet; do not edit, used internally");
1476
1477 // used both as source and sync property, internal in both cases
1478 static SafeConfigProperty syncPropDeviceData("deviceData",
1479                                              "information about the peer in the format described in the\n"
1480                                              "Synthesis SDK manual under 'Session_SaveDeviceInfo'");
1481
1482 static SafeConfigProperty globalPropDefaultPeer("defaultPeer",
1483                                                 "the peer which is used by default in some frontends, like the sync-UI");
1484
1485 static ConfigProperty globalPropKeyring("keyring",
1486                                         "Explicitly selects a certain safe password storage.\n"
1487                                         "Depending on how SyncEvolution was compiled and installed\n"
1488                                         "the following values are possible:\n"
1489                                         "\n"
1490                                         "GNOME\n  GNOME Keyring\n"
1491                                         "KDE\n  KWallet\n"
1492                                         "yes/true/1\n  pick one automatically\n"
1493                                         "no/false/0\n  store passwords in SyncEvolution config files\n"
1494                                         "\n"
1495                                         "If unset, the default is to pick one automatically in\n"
1496                                         "the D-Bus server and not use any keyring in the command\n"
1497                                         "tool when running without that D-Bus server (because the\n"
1498                                         "keyring might not be usable without a desktop session).\n"
1499                                         "If support for only storage was compiled and installed,\n"
1500                                         "then that is the one which gets picked. Otherwise the\n"
1501                                         "default is to use GNOME Keyring (because distinguishing\n"
1502                                         "between KDE and GNOME sessions automatically is tricky).\n"
1503                                         "\n"
1504                                         "Note that using this option applies to *all* passwords in\n"
1505                                         "a configuration and that the --keyring command line option\n"
1506                                         "is merely an alias for setting the global property, so setting\n"
1507                                         "a single password as follows sets both `keyring` and\n"
1508                                         "`proxyPasswords`, and also moves the other passwords into the\n"
1509                                         "keyring, even if they were not stored there already:\n"
1510                                         "\n"
1511                                         "     --keyring --configure proxyPassword=foo\n"
1512                                         "\n"
1513                                         "When passwords were stored in the keyring, their value is set to a single\n"
1514                                         "hyphen (\"-\") in the configuration. This means that when running a\n"
1515                                         "synchronization without the --keyring argument, the password has to be\n"
1516                                         "entered interactively. The --print-config output always shows \"-\" instead\n"
1517                                         "of retrieving the password from the keyring.\n",
1518                                         "yes");
1519
1520 static StringConfigProperty syncPropAutoSync("autoSync",
1521                                              "Controls automatic synchronization. Currently,\n"
1522                                              "automatic synchronization is done by running\n"
1523                                              "a synchronization at regular intervals. This\n"
1524                                              "may drain the battery, in particular when\n"
1525                                              "using Bluetooth!\n"
1526                                              "Because a peer might be reachable via different\n"
1527                                              "transports at some point, this option provides\n"
1528                                              "detailed control over which transports may\n"
1529                                              "be used for automatic synchronization:\n\n"
1530                                              "0\n  don't do auto sync\n"
1531                                              "1\n  do automatic sync, using whatever transport\n"
1532                                              "    is available\n"
1533                                              "http\n  only via HTTP transport\n"
1534                                              "obex-bt\n  only via Bluetooth transport\n"
1535                                              "http,obex-bt\n  pick one of these\n",
1536                                              "0");
1537
1538 static SecondsConfigProperty syncPropAutoSyncInterval("autoSyncInterval",
1539                                                       "This is the minimum number of seconds between two\n"
1540                                                       "synchronizations that has to pass before starting\n"
1541                                                       "an automatic synchronization. Can be specified using\n"
1542                                                       "a 1h30m5s format.\n"
1543                                                       "\n"
1544                                                       "Before reducing this interval, consider that it will\n"
1545                                                       "increase resource consumption on the local and remote\n"
1546                                                       "side. Some SyncML server operators only allow a\n"
1547                                                       "certain number of sessions per day.\n"
1548                                                       "The value 0 has the effect of only running automatic\n"
1549                                                       "synchronization when changes are detected (not\n"
1550                                                       "implemented yet, therefore it basically disables\n"
1551                                                       "automatic synchronization).\n",
1552                                                       "30M");
1553
1554 static SecondsConfigProperty syncPropAutoSyncDelay("autoSyncDelay",
1555                                                    "An automatic sync will not be started unless the peer\n"
1556                                                    "has been available for this duration, specified in seconds\n"
1557                                                    "or 1h30m5s format.\n"
1558                                                    "\n"
1559                                                    "This prevents running a sync when network connectivity\n"
1560                                                    "is unreliable or was recently established for some\n"
1561                                                    "other purpose. It is also a heuristic that attempts\n"
1562                                                    "to predict how long connectivity be available in the\n"
1563                                                    "future, because it should better be available long\n"
1564                                                    "enough to complete the synchronization.\n",
1565                                                    "5M");
1566
1567 /* config and on-disk file versionsing */
1568 static IntConfigProperty propRootMinVersion("rootMinVersion", "");
1569 static IntConfigProperty propRootCurVersion("rootCurVersion", "");
1570 static IntConfigProperty propContextMinVersion("contextMinVersion", "");
1571 static IntConfigProperty propContextCurVersion("contextCurVersion", "");
1572 static IntConfigProperty propPeerMinVersion("peerMinVersion", "");
1573 static IntConfigProperty propPeerCurVersion("peerCurVersion", "");
1574
1575 static const IntConfigProperty *configVersioning[CONFIG_LEVEL_MAX][CONFIG_VERSION_MAX] = {
1576     { &propRootMinVersion, &propRootCurVersion },
1577     { &propContextMinVersion, &propContextCurVersion },
1578     { &propPeerMinVersion, &propPeerCurVersion }
1579 };
1580
1581 static const IntConfigProperty &getConfigVersionProp(ConfigLevel level, ConfigLimit limit)
1582 {
1583     if (level < 0 || level >= CONFIG_LEVEL_MAX ||
1584         limit < 0 || limit >= CONFIG_VERSION_MAX) {
1585         SE_THROW("getConfigVersionProp: invalid args");
1586     }
1587     return *configVersioning[level][limit];
1588 }
1589
1590 int SyncConfig::getConfigVersion(ConfigLevel level, ConfigLimit limit) const
1591 {
1592     const IntConfigProperty &prop = getConfigVersionProp(level, limit);
1593     return prop.getPropertyValue(*getNode(prop));
1594 }
1595
1596 void SyncConfig::setConfigVersion(ConfigLevel level, ConfigLimit limit, int version)
1597 {
1598     if (m_layout != SHARED_LAYOUT) {
1599         // old-style layouts have version 0 by default, no need
1600         // (and sometimes no possibility) to set this explicitly
1601         if (version != 0) {
1602             SE_THROW(StringPrintf("cannot bump config version in old-style config %s", m_peer.c_str()));
1603         }
1604     } else {
1605         const IntConfigProperty &prop = getConfigVersionProp(level, limit);
1606         prop.setProperty(*getNode(prop), version);
1607     }
1608 }
1609
1610
1611
1612 /**
1613  * This constructor updates some of the properties above and then adds
1614  * them to the registry. This cannot be done inside getRegistry()
1615  * itself because that function may be invoked by other global
1616  * instances before the properties above were constructed (BMC
1617  * #19464).
1618  */
1619 static class RegisterSyncConfigProperties
1620 {
1621 public:
1622     RegisterSyncConfigProperties()
1623     {
1624         ConfigPropertyRegistry &registry = SyncConfig::getRegistry();
1625
1626         // temporarily move existing properties away so that the important
1627         // standard properties come first when using the traditional
1628         // push_back() way of adding them
1629         ConfigPropertyRegistry tmp;
1630         std::swap(registry, tmp);
1631
1632         registry.push_back(&syncPropSyncURL);
1633         registry.push_back(&syncPropUsername);
1634         registry.push_back(&syncPropPassword);
1635         registry.push_back(&syncPropLogDir);
1636         registry.push_back(&syncPropLogLevel);
1637         registry.push_back(&syncPropNotifyLevel);
1638         registry.push_back(&syncPropPrintChanges);
1639         registry.push_back(&syncPropDumpData);
1640         registry.push_back(&syncPropMaxLogDirs);
1641         registry.push_back(&syncPropAutoSync);
1642         registry.push_back(&syncPropAutoSyncInterval);
1643         registry.push_back(&syncPropAutoSyncDelay);
1644         registry.push_back(&syncPropPreventSlowSync);
1645         registry.push_back(&syncPropUseProxy);
1646         registry.push_back(&syncPropProxyHost);
1647         registry.push_back(&syncPropProxyUsername);
1648         registry.push_back(&syncPropProxyPassword);
1649         registry.push_back(&syncPropClientAuthType);
1650         registry.push_back(&syncPropRetryDuration);
1651         registry.push_back(&syncPropRetryInterval);
1652         registry.push_back(&syncPropRemoteIdentifier);
1653         registry.push_back(&syncPropPeerIsClient);
1654         registry.push_back(&syncPropSyncMLVersion);
1655         registry.push_back(&syncPropPeerName);
1656         registry.push_back(&syncPropDevID);
1657         registry.push_back(&syncPropRemoteDevID);
1658         registry.push_back(&syncPropWBXML);
1659         registry.push_back(&syncPropRefreshSync);
1660         registry.push_back(&syncPropMaxMsgSize);
1661         registry.push_back(&syncPropMaxObjSize);
1662         registry.push_back(&syncPropSSLServerCertificates);
1663         registry.push_back(&syncPropSSLVerifyServer);
1664         registry.push_back(&syncPropSSLVerifyHost);
1665         registry.push_back(&syncPropWebURL);
1666         registry.push_back(&syncPropIconURI);
1667         registry.push_back(&syncPropConsumerReady);
1668         registry.push_back(&syncPropPeerType);
1669         registry.push_back(&syncPropHashCode);
1670         registry.push_back(&syncPropConfigDate);
1671         registry.push_back(&syncPropNonce);
1672         registry.push_back(&syncPropDeviceData);
1673         registry.push_back(&globalPropDefaultPeer);
1674         registry.push_back(&globalPropKeyring);
1675
1676 #if 0
1677         // Must not be registered! Not valid for --sync-property and
1678         // must not be copied between configs.
1679         registry.push_back(&syncPropRootMinVersion);
1680         registry.push_back(&syncPropRootCurVersion);
1681         registry.push_back(&syncPropContextMinVersion);
1682         registry.push_back(&syncPropContextCurVersion);
1683         registry.push_back(&syncPropPeerMinVersion);
1684         registry.push_back(&syncPropPeerCurVersion);
1685 #endif
1686
1687         BOOST_FOREACH (const ConfigProperty *prop, tmp) {
1688             registry.push_back(prop);
1689         }
1690
1691         // obligatory sync properties
1692         //
1693         // username/password used to be
1694         // considered obligatory, but are not anymore because there are
1695         // cases where they are not needed (local sync, Bluetooth)
1696         // syncPropUsername.setObligatory(true);
1697         // syncPropPassword.setObligatory(true);
1698         //
1699         // created if not given:
1700         // syncPropDevID.setObligatory(true);
1701         syncPropSyncURL.setObligatory(true);
1702
1703         // hidden sync properties
1704         syncPropHashCode.setHidden(true);
1705         syncPropConfigDate.setHidden(true);
1706         syncPropNonce.setHidden(true);
1707         syncPropDeviceData.setHidden(true);
1708         propRootMinVersion.setHidden(true);
1709         propRootCurVersion.setHidden(true);
1710         propContextMinVersion.setHidden(true);
1711         propContextCurVersion.setHidden(true);
1712         propPeerMinVersion.setHidden(true);
1713         propPeerCurVersion.setHidden(true);
1714
1715         // global sync properties
1716         globalPropDefaultPeer.setSharing(ConfigProperty::GLOBAL_SHARING);
1717         globalPropKeyring.setSharing(ConfigProperty::GLOBAL_SHARING);
1718         propRootMinVersion.setSharing(ConfigProperty::GLOBAL_SHARING);
1719         propRootCurVersion.setSharing(ConfigProperty::GLOBAL_SHARING);
1720
1721         // peer independent sync properties
1722         syncPropLogDir.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1723         syncPropMaxLogDirs.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1724         syncPropDevID.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1725         propContextMinVersion.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1726         propContextCurVersion.setSharing(ConfigProperty::SOURCE_SET_SHARING);
1727     }
1728 } RegisterSyncConfigProperties;
1729
1730 ConfigPropertyRegistry &SyncConfig::getRegistry()
1731 {
1732     static ConfigPropertyRegistry registry;
1733     return registry;
1734 }
1735
1736 InitStateString SyncConfig::getSyncUsername() const { return syncPropUsername.getProperty(*getNode(syncPropUsername)); }
1737 void SyncConfig::setSyncUsername(const string &value, bool temporarily) { syncPropUsername.setProperty(*getNode(syncPropUsername), value, temporarily); }
1738 InitStateString SyncConfig::getSyncPassword() const {
1739     return syncPropPassword.getCachedProperty(*getNode(syncPropPassword), m_cachedPassword);
1740 }
1741 void PasswordConfigProperty::checkPassword(UserInterface &ui,
1742                                            const string &serverName,
1743                                            FilterConfigNode &globalConfigNode,
1744                                            const string &sourceName,
1745                                            const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1746 {
1747     string password, passwordSave;
1748     /* if no source config node, then it should only be password in the global config node */
1749     if(sourceConfigNode.get() == NULL) {
1750         password = getProperty(globalConfigNode);
1751     } else {
1752         password = getProperty(*sourceConfigNode);
1753     }
1754
1755     string descr = getDescr(serverName,globalConfigNode,sourceName,sourceConfigNode);
1756     if (password == "-") {
1757         ConfigPasswordKey key = getPasswordKey(descr,serverName,globalConfigNode,sourceName,sourceConfigNode);
1758         passwordSave = ui.askPassword(getMainName(),descr, key);
1759     } else if(boost::starts_with(password, "${") &&
1760               boost::ends_with(password, "}")) {
1761         string envname = password.substr(2, password.size() - 3);
1762         const char *envval = getenv(envname.c_str());
1763         if (!envval) {
1764             SyncContext::throwError(string("the environment variable '") +
1765                                             envname +
1766                                             "' for the '" +
1767                                             descr +
1768                                             "' password is not set");
1769         } else {
1770             passwordSave = envval;
1771         }
1772     }
1773     /* If password is from ui or environment variable, set them in the config node on fly
1774      * Previous impl use temp string to store them, this is not good for expansion in the backend */
1775     if(!passwordSave.empty()) {
1776         if(sourceConfigNode.get() == NULL) {
1777             globalConfigNode.addFilter(getMainName(), InitStateString(passwordSave, true));
1778         } else {
1779             sourceConfigNode->addFilter(getMainName(), InitStateString(passwordSave, true));
1780         }
1781     }
1782 }
1783 void PasswordConfigProperty::savePassword(UserInterface &ui,
1784                                           const string &serverName,
1785                                           FilterConfigNode &globalConfigNode,
1786                                           const string &sourceName,
1787                                           const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1788 {
1789     /** here we don't invoke askPassword for this function has different logic from it */
1790     string password;
1791     if(sourceConfigNode.get() == NULL) {
1792         password = getProperty(globalConfigNode);
1793     } else {
1794         password = getProperty(*sourceConfigNode);
1795     }
1796     /** if it has been stored or it has no value, do nothing */
1797     if(password == "-" || password == "") {
1798         return;
1799     } else if(boost::starts_with(password, "${") &&
1800               boost::ends_with(password, "}")) {
1801         /** we delay this calculation of environment variable for 
1802          * it might be changed in the sync time. */
1803         return;
1804     }
1805     string descr = getDescr(serverName,globalConfigNode,sourceName,sourceConfigNode);
1806     ConfigPasswordKey key = getPasswordKey(descr,serverName,globalConfigNode,sourceName,sourceConfigNode);
1807     if(ui.savePassword(getMainName(), password, key)) {
1808         string value = "-";
1809         if(sourceConfigNode.get() == NULL) {
1810             setProperty(globalConfigNode, value);
1811         } else {
1812             setProperty(*sourceConfigNode,value);
1813         }
1814     }
1815 }
1816
1817 InitStateString PasswordConfigProperty::getCachedProperty(const ConfigNode &node,
1818                                                           const string &cachedPassword)
1819 {
1820     InitStateString password;
1821
1822     if (!cachedPassword.empty()) {
1823         password = InitStateString(cachedPassword, true);
1824     } else {
1825         password = getProperty(node);
1826     }
1827     return password;
1828 }
1829
1830 /**
1831  * remove some unnecessary parts of server URL.
1832  * internal use.
1833  */
1834 static void purifyServer(string &server)
1835 {
1836     /** here we use server sync url without protocol prefix and
1837      * user account name as the key in the keyring */
1838     size_t start = server.find("://");
1839     /** we don't reserve protocol prefix for it may change*/
1840     if(start != server.npos) {
1841         server = server.substr(start + 3);
1842     }
1843 }
1844
1845 ConfigPasswordKey PasswordConfigProperty::getPasswordKey(const string &descr,
1846                                                          const string &serverName,
1847                                                          FilterConfigNode &globalConfigNode,
1848                                                          const string &sourceName,
1849                                                          const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const 
1850 {
1851     ConfigPasswordKey key;
1852     key.server = syncPropSyncURL.getProperty(globalConfigNode);
1853     purifyServer(key.server);
1854     key.user   = syncPropUsername.getProperty(globalConfigNode);
1855     return key;
1856 }
1857 void ProxyPasswordConfigProperty::checkPassword(UserInterface &ui,
1858                                            const string &serverName,
1859                                            FilterConfigNode &globalConfigNode,
1860                                            const string &sourceName,
1861                                            const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1862 {
1863     /* if useProxy is set 'true', then check proxypassword */
1864     if(syncPropUseProxy.getPropertyValue(globalConfigNode)) {
1865         PasswordConfigProperty::checkPassword(ui, serverName, globalConfigNode, sourceName, sourceConfigNode);
1866     }
1867 }
1868
1869 ConfigPasswordKey ProxyPasswordConfigProperty::getPasswordKey(const string &descr,
1870                                                               const string &serverName,
1871                                                               FilterConfigNode &globalConfigNode,
1872                                                               const string &sourceName,
1873                                                               const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
1874 {
1875     ConfigPasswordKey key;
1876     key.server = syncPropProxyHost.getProperty(globalConfigNode);
1877     key.user   = syncPropProxyUsername.getProperty(globalConfigNode);
1878     return key;
1879 }
1880
1881 void SyncConfig::setSyncPassword(const string &value, bool temporarily) { m_cachedPassword = ""; syncPropPassword.setProperty(*getNode(syncPropPassword), value, temporarily); }
1882
1883 InitState<bool> SyncConfig::getPreventSlowSync() const {
1884     return syncPropPreventSlowSync.getPropertyValue(*getNode(syncPropPreventSlowSync));
1885 }
1886 void SyncConfig::setPreventSlowSync(bool value, bool temporarily) { syncPropPreventSlowSync.setProperty(*getNode(syncPropPreventSlowSync), value, temporarily); }
1887
1888 static const char *ProxyString = "http_proxy";
1889
1890 /* Reads http_proxy from environment, if not available returns configured value */
1891 InitState<bool> SyncConfig::getUseProxy() const {
1892     InitState<bool> res = syncPropUseProxy.getPropertyValue(*getNode(syncPropUseProxy));
1893     if (!res.wasSet()) {
1894         // Not configured. Check environment.
1895         char *proxy = getenv(ProxyString);
1896         if (proxy && *proxy) {
1897             // Environment has it. Assume that it applies to us and
1898             // use it. TODO: check no_proxy against our sync URL.
1899             res = InitState<bool>(true, true);
1900         }
1901     }
1902     return res;
1903 }
1904
1905 void SyncConfig::setUseProxy(bool value, bool temporarily) { syncPropUseProxy.setProperty(*getNode(syncPropUseProxy), value, temporarily); }
1906
1907 /* If http_proxy set in the environment returns it, otherwise configured value */
1908 InitStateString SyncConfig::getProxyHost() const {
1909     char *proxy = getenv(ProxyString);
1910     if (!proxy) {
1911         return syncPropProxyHost.getProperty(*getNode(syncPropUseProxy)); 
1912     } else {
1913         return InitStateString(proxy, true);
1914     }
1915 }
1916
1917 void SyncConfig::setProxyHost(const string &value, bool temporarily) { syncPropProxyHost.setProperty(*getNode(syncPropProxyHost), value, temporarily); }
1918
1919 InitStateString SyncConfig::getProxyUsername() const { return syncPropProxyUsername.getProperty(*getNode(syncPropProxyUsername)); }
1920 void SyncConfig::setProxyUsername(const string &value, bool temporarily) { syncPropProxyUsername.setProperty(*getNode(syncPropProxyUsername), value, temporarily); }
1921
1922 InitStateString SyncConfig::getProxyPassword() const {
1923     return syncPropProxyPassword.getCachedProperty(*getNode(syncPropProxyPassword), m_cachedProxyPassword);
1924 }
1925 void SyncConfig::setProxyPassword(const string &value, bool temporarily) { m_cachedProxyPassword = ""; syncPropProxyPassword.setProperty(*getNode(syncPropProxyPassword), value, temporarily); }
1926 InitState< vector<string> > SyncConfig::getSyncURL() const { 
1927     InitStateString s = syncPropSyncURL.getProperty(*getNode(syncPropSyncURL));
1928     vector<string> urls;
1929     if (!s.empty()) {
1930         // workaround for g++ 4.3/4.4:
1931         // http://stackoverflow.com/questions/1168525/c-gcc4-4-warning-array-subscript-is-above-array-bounds
1932         static const string sep(" \t");
1933         boost::split(urls, s.get(), boost::is_any_of(sep));
1934     }
1935     return InitState< vector<string> >(urls, s.wasSet());
1936 }
1937 void SyncConfig::setSyncURL(const string &value, bool temporarily) { syncPropSyncURL.setProperty(*getNode(syncPropSyncURL), value, temporarily); }
1938 void SyncConfig::setSyncURL(const vector<string> &value, bool temporarily) { 
1939     stringstream urls;
1940     BOOST_FOREACH (string url, value) {
1941         urls<<url<<" ";
1942     }
1943     return setSyncURL (urls.str(), temporarily);
1944 }
1945 InitStateString SyncConfig::getClientAuthType() const { return syncPropClientAuthType.getProperty(*getNode(syncPropClientAuthType)); }
1946 void SyncConfig::setClientAuthType(const string &value, bool temporarily) { syncPropClientAuthType.setProperty(*getNode(syncPropClientAuthType), value, temporarily); }
1947 InitState<unsigned long > SyncConfig::getMaxMsgSize() const { return syncPropMaxMsgSize.getPropertyValue(*getNode(syncPropMaxMsgSize)); }
1948 void SyncConfig::setMaxMsgSize(unsigned long value, bool temporarily) { syncPropMaxMsgSize.setProperty(*getNode(syncPropMaxMsgSize), value, temporarily); }
1949 InitState<unsigned int > SyncConfig::getMaxObjSize() const { return syncPropMaxObjSize.getPropertyValue(*getNode(syncPropMaxObjSize)); }
1950 void SyncConfig::setMaxObjSize(unsigned int value, bool temporarily) { syncPropMaxObjSize.setProperty(*getNode(syncPropMaxObjSize), value, temporarily); }
1951 InitStateString SyncConfig::getDevID() const { return syncPropDevID.getProperty(*getNode(syncPropDevID)); }
1952 void SyncConfig::setDevID(const string &value, bool temporarily) { syncPropDevID.setProperty(*getNode(syncPropDevID), value, temporarily); }
1953 InitState<bool> SyncConfig::getWBXML() const { return syncPropWBXML.getPropertyValue(*getNode(syncPropWBXML)); }
1954 void SyncConfig::setWBXML(bool value, bool temporarily) { syncPropWBXML.setProperty(*getNode(syncPropWBXML), value, temporarily); }
1955 InitState<bool> SyncConfig::getRefreshSync() const { return syncPropRefreshSync.getPropertyValue(*getNode(syncPropRefreshSync)); }
1956 void SyncConfig::setRefreshSync(bool value, bool temporarily) { syncPropRefreshSync.setProperty(*getNode(syncPropRefreshSync), value, temporarily); }
1957 InitStateString SyncConfig::getLogDir() const { return syncPropLogDir.getProperty(*getNode(syncPropLogDir)); }
1958 void SyncConfig::setLogDir(const string &value, bool temporarily) { syncPropLogDir.setProperty(*getNode(syncPropLogDir), value, temporarily); }
1959 InitState<unsigned int> SyncConfig::getMaxLogDirs() const { return syncPropMaxLogDirs.getPropertyValue(*getNode(syncPropMaxLogDirs)); }
1960 void SyncConfig::setMaxLogDirs(unsigned int value, bool temporarily) { syncPropMaxLogDirs.setProperty(*getNode(syncPropMaxLogDirs), value, temporarily); }
1961 InitState<unsigned int> SyncConfig::getLogLevel() const { return syncPropLogLevel.getPropertyValue(*getNode(syncPropLogLevel)); }
1962 void SyncConfig::setLogLevel(unsigned int value, bool temporarily) { syncPropLogLevel.setProperty(*getNode(syncPropLogLevel), value, temporarily); }
1963 InitState<SyncConfig::NotifyLevel> SyncConfig::getNotifyLevel() const {
1964     InitState<unsigned int> res = syncPropNotifyLevel.getPropertyValue(*getNode(syncPropNotifyLevel));
1965     return InitState<SyncConfig::NotifyLevel>(static_cast<SyncConfig::NotifyLevel>(res.get()), res.wasSet());
1966 }
1967 void SyncConfig::setNotifyLevel(SyncConfig::NotifyLevel value, bool temporarily) { syncPropNotifyLevel.setProperty(*getNode(syncPropNotifyLevel), value, temporarily); }
1968 InitState<unsigned int> SyncConfig::getRetryDuration() const {return syncPropRetryDuration.getPropertyValue(*getNode(syncPropRetryDuration));}
1969 void SyncConfig::setRetryDuration(unsigned int value, bool temporarily) { syncPropRetryDuration.setProperty(*getNode(syncPropRetryDuration), value, temporarily); }
1970 InitState<unsigned int> SyncConfig::getRetryInterval() const { return syncPropRetryInterval.getPropertyValue(*getNode(syncPropRetryInterval)); }
1971 void SyncConfig::setRetryInterval(unsigned int value, bool temporarily) { return syncPropRetryInterval.setProperty(*getNode(syncPropRetryInterval),value,temporarily); }
1972
1973 /* used by Server Alerted Sync */
1974 InitStateString SyncConfig::getRemoteIdentifier() const { return syncPropRemoteIdentifier.getProperty(*getNode(syncPropRemoteIdentifier));}
1975 void SyncConfig::setRemoteIdentifier (const string &value, bool temporarily) { return syncPropRemoteIdentifier.setProperty (*getNode(syncPropRemoteIdentifier), value, temporarily); }
1976
1977 InitState<bool> SyncConfig::getPeerIsClient() const { return syncPropPeerIsClient.getPropertyValue(*getNode(syncPropPeerIsClient)); }
1978 void SyncConfig::setPeerIsClient(bool value, bool temporarily) { syncPropPeerIsClient.setProperty(*getNode(syncPropPeerIsClient), value, temporarily); }
1979
1980 InitStateString SyncConfig::getSyncMLVersion() const {
1981     InitState< std::set<std::string> > flags = getSyncMLFlags();
1982     static const char * const versions[] = { "1.2", "1.1", "1.0" };
1983     BOOST_FOREACH (const char *version, versions) {
1984         if (flags.find(version) != flags.end()) {
1985             return InitStateString(version, flags.wasSet());
1986         }
1987     }
1988     return InitStateString("", flags.wasSet());
1989 }
1990 InitState<unsigned int> SyncConfig::getRequestMaxTime() const {
1991     InitState<unsigned int> requestmaxtime;
1992     InitState< std::set<std::string> > flags = getSyncMLFlags();
1993     BOOST_FOREACH(const std::string &flag, flags) {
1994         size_t offset = flag.find('=');
1995         if (offset != flag.npos) {
1996             std::string key = flag.substr(0, offset);
1997             if (boost::iequals(key, "RequestMaxTime")) {
1998                 std::string value = flag.substr(offset + 1);
1999                 unsigned int seconds;
2000                 std::string error;
2001                 if (!SecondsConfigProperty::parseDuration(value, error, seconds)) {
2002                     SE_THROW("invalid RequestMaxTime value in SyncMLVersion property: " + error);
2003                 }
2004                 requestmaxtime = seconds;
2005                 break;
2006             }
2007         }
2008     }
2009     return requestmaxtime;
2010 }
2011 InitState< std::set<std::string> > SyncConfig::getSyncMLFlags() const {
2012     InitStateString value = syncPropSyncMLVersion.getProperty(*getNode(syncPropSyncMLVersion));
2013     std::list<std::string> keywords;
2014     // Work around g++ 4.4 + "array out of bounds" when using is_any_of() on plain string.
2015     static const std::string delim(" ,");
2016     boost::split(keywords, value, boost::is_any_of(delim));
2017     std::set<std::string> flags;
2018     BOOST_FOREACH (std::string &keyword, keywords) {
2019         boost::to_lower(keyword);
2020         flags.insert(keyword);
2021     }
2022     return InitState< std::set<std::string> >(flags, value.wasSet());
2023 }
2024
2025 InitStateString SyncConfig::getUserPeerName() const { return syncPropPeerName.getProperty(*getNode(syncPropPeerName)); }
2026 void SyncConfig::setUserPeerName(const InitStateString &name) { syncPropPeerName.setProperty(*getNode(syncPropPeerName), name); }
2027
2028 InitState<bool> SyncConfig::getPrintChanges() const { return syncPropPrintChanges.getPropertyValue(*getNode(syncPropPrintChanges)); }
2029 void SyncConfig::setPrintChanges(bool value, bool temporarily) { syncPropPrintChanges.setProperty(*getNode(syncPropPrintChanges), value, temporarily); }
2030 InitState<bool> SyncConfig::getDumpData() const { return syncPropDumpData.getPropertyValue(*getNode(syncPropDumpData)); }
2031 void SyncConfig::setDumpData(bool value, bool temporarily) { syncPropDumpData.setProperty(*getNode(syncPropDumpData), value, temporarily); }
2032 InitStateString SyncConfig::getWebURL() const { return syncPropWebURL.getProperty(*getNode(syncPropWebURL)); }
2033 void SyncConfig::setWebURL(const std::string &url, bool temporarily) { syncPropWebURL.setProperty(*getNode(syncPropWebURL), url, temporarily); }
2034 InitStateString SyncConfig::getIconURI() const { return syncPropIconURI.getProperty(*getNode(syncPropIconURI)); }
2035 InitState<bool> SyncConfig::getConsumerReady() const { return syncPropConsumerReady.getPropertyValue(*getNode(syncPropConsumerReady)); }
2036 void SyncConfig::setConsumerReady(bool ready) { return syncPropConsumerReady.setProperty(*getNode(syncPropConsumerReady), ready); }
2037 void SyncConfig::setIconURI(const std::string &uri, bool temporarily) { syncPropIconURI.setProperty(*getNode(syncPropIconURI), uri, temporarily); }
2038 InitState<unsigned long> SyncConfig::getHashCode() const { return syncPropHashCode.getPropertyValue(*getNode(syncPropHashCode)); }
2039 void SyncConfig::setHashCode(unsigned long code) { syncPropHashCode.setProperty(*getNode(syncPropHashCode), code); }
2040 InitStateString SyncConfig::getConfigDate() const { return syncPropConfigDate.getProperty(*getNode(syncPropConfigDate)); }
2041 void SyncConfig::setConfigDate() { 
2042     /* Set current timestamp as configdate */
2043     char buffer[17]; 
2044     time_t ts = time(NULL);
2045     strftime(buffer, sizeof(buffer), "%Y%m%dT%H%M%SZ", gmtime(&ts));
2046     const std::string date(buffer);
2047     syncPropConfigDate.setProperty(*getNode(syncPropConfigDate), date);
2048 }
2049
2050 InitStateString SyncConfig::getSSLServerCertificates() const { return syncPropSSLServerCertificates.getProperty(*getNode(syncPropSSLServerCertificates)); }
2051 void SyncConfig::setSSLServerCertificates(const string &value, bool temporarily) { syncPropSSLServerCertificates.setProperty(*getNode(syncPropSSLServerCertificates), value, temporarily); }
2052 InitState<bool> SyncConfig::getSSLVerifyServer() const { return syncPropSSLVerifyServer.getPropertyValue(*getNode(syncPropSSLVerifyServer)); }
2053 void SyncConfig::setSSLVerifyServer(bool value, bool temporarily) { syncPropSSLVerifyServer.setProperty(*getNode(syncPropSSLVerifyServer), value, temporarily); }
2054 InitState<bool> SyncConfig::getSSLVerifyHost() const { return syncPropSSLVerifyHost.getPropertyValue(*getNode(syncPropSSLVerifyHost)); }
2055 void SyncConfig::setSSLVerifyHost(bool value, bool temporarily) { syncPropSSLVerifyHost.setProperty(*getNode(syncPropSSLVerifyHost), value, temporarily); }
2056 InitStateString SyncConfig::getRemoteDevID() const { return syncPropRemoteDevID.getProperty(*getNode(syncPropRemoteDevID)); }
2057 void SyncConfig::setRemoteDevID(const string &value) { syncPropRemoteDevID.setProperty(*getNode(syncPropRemoteDevID), value); }
2058 InitStateString SyncConfig::getNonce() const { return syncPropNonce.getProperty(*getNode(syncPropNonce)); }
2059 void SyncConfig::setNonce(const string &value) { syncPropNonce.setProperty(*getNode(syncPropNonce), value); }
2060 InitStateString SyncConfig::getDeviceData() const { return syncPropDeviceData.getProperty(*getNode(syncPropDeviceData)); }
2061 void SyncConfig::setDeviceData(const string &value) { syncPropDeviceData.setProperty(*getNode(syncPropDeviceData), value); }
2062 InitStateString SyncConfig::getDefaultPeer() const { return globalPropDefaultPeer.getProperty(*getNode(globalPropDefaultPeer)); }
2063 void SyncConfig::setDefaultPeer(const string &value) { globalPropDefaultPeer.setProperty(*getNode(globalPropDefaultPeer), value); }
2064 InitStateTri SyncConfig::getKeyring() const { return globalPropKeyring.getProperty(*getNode(globalPropKeyring)); }
2065 void SyncConfig::setKeyring(const string &value) { globalPropKeyring.setProperty(*getNode(globalPropKeyring), value); }
2066
2067 InitStateString SyncConfig::getAutoSync() const { return syncPropAutoSync.getProperty(*getNode(syncPropAutoSync)); }
2068 void SyncConfig::setAutoSync(const string &value, bool temporarily) { syncPropAutoSync.setProperty(*getNode(syncPropAutoSync), value, temporarily); }
2069 InitState<unsigned int> SyncConfig::getAutoSyncInterval() const { return syncPropAutoSyncInterval.getPropertyValue(*getNode(syncPropAutoSyncInterval)); }
2070 void SyncConfig::setAutoSyncInterval(unsigned int value, bool temporarily) { syncPropAutoSyncInterval.setProperty(*getNode(syncPropAutoSyncInterval), value, temporarily); }
2071 InitState<unsigned int> SyncConfig::getAutoSyncDelay() const { return syncPropAutoSyncDelay.getPropertyValue(*getNode(syncPropAutoSyncDelay)); }
2072 void SyncConfig::setAutoSyncDelay(unsigned int value, bool temporarily) { syncPropAutoSyncDelay.setProperty(*getNode(syncPropAutoSyncDelay), value, temporarily); }
2073
2074 std::string SyncConfig::findSSLServerCertificate()
2075 {
2076     std::string paths = getSSLServerCertificates();
2077     std::vector< std::string > files;
2078     boost::split(files, paths, boost::is_any_of(":"));
2079     BOOST_FOREACH(std::string file, files) {
2080         if (!file.empty() && !access(file.c_str(), R_OK)) {
2081             return file;
2082         }
2083     }
2084
2085     return "";
2086 }
2087
2088 void SyncConfig::setConfigFilter(bool sync,
2089                                  const std::string &source,
2090                                  const FilterConfigNode::ConfigFilter &filter)
2091 {
2092     if (sync) {
2093         m_peerNode->setFilter(filter);
2094         if (m_peerNode != m_contextNode) {
2095             m_contextNode->setFilter(filter);
2096         }
2097         if (m_globalNode != m_contextNode) {
2098             m_globalNode->setFilter(filter);
2099         }
2100     } else {
2101         m_nodeCache.clear();
2102         m_sourceFilters[source] = filter;
2103     }
2104 }
2105
2106 boost::shared_ptr<FilterConfigNode>
2107 SyncConfig::getNode(const ConfigProperty &prop)
2108 {
2109     switch (prop.getSharing()) {
2110     case ConfigProperty::GLOBAL_SHARING:
2111         if (prop.isHidden()) {
2112             return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_globalHiddenNode));
2113         } else {
2114             return m_globalNode;
2115         }
2116         break;
2117     case ConfigProperty::SOURCE_SET_SHARING:
2118         if (prop.isHidden()) {
2119             return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_contextHiddenNode));
2120         } else {
2121             return m_contextNode;
2122         }
2123         break;
2124     case ConfigProperty::NO_SHARING:
2125         if (prop.isHidden()) {
2126             return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_hiddenPeerNode));
2127         } else {
2128             return m_peerNode;
2129         }
2130         break;
2131     }
2132     // should not be reached
2133     return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("unknown sharing state of property"))));
2134 }
2135
2136 boost::shared_ptr<FilterConfigNode>
2137 SyncConfig::getNode(const std::string &propName)
2138 {
2139     ConfigPropertyRegistry &registry = getRegistry();
2140     const ConfigProperty *prop = registry.find(propName);
2141     if (prop) {
2142         return getNode(*prop);
2143     } else {
2144         return boost::shared_ptr<FilterConfigNode>();
2145     }
2146 }
2147
2148 static void setDefaultProps(const ConfigPropertyRegistry &registry,
2149                             boost::shared_ptr<FilterConfigNode> node,
2150                             bool force,
2151                             bool unshared,
2152                             bool useObligatory = true)
2153 {
2154     BOOST_FOREACH(const ConfigProperty *prop, registry) {
2155         InitStateString value = prop->getProperty(*node);
2156         if (!prop->isHidden() &&
2157             (unshared || prop->getSharing() != ConfigProperty::NO_SHARING) &&
2158             (force || !value.wasSet())) {
2159             if (useObligatory) {
2160                 prop->setDefaultProperty(*node, prop->isObligatory());
2161             } else {
2162                 prop->setDefaultProperty(*node, false);
2163             }
2164         }
2165     }
2166 }
2167
2168 void SyncConfig::setDefaults(bool force)
2169 {
2170     setDefaultProps(getRegistry(), getProperties(),
2171                     force,
2172                     !m_peerPath.empty());
2173 }
2174
2175 void SyncConfig::setSourceDefaults(const string &name, bool force)
2176 {
2177     SyncSourceNodes nodes = getSyncSourceNodes(name);
2178     setDefaultProps(SyncSourceConfig::getRegistry(),
2179                     nodes.getProperties(),
2180                     force,
2181                     !m_peerPath.empty());
2182 }
2183
2184 void SyncConfig::removeSyncSource(const string &name)
2185 {
2186     string lower = name;
2187     boost::to_lower(lower);
2188     string pathName;
2189
2190     if (m_layout == SHARED_LAYOUT) {
2191         if (m_peerPath.empty()) {
2192             // removed shared source properties...
2193             pathName = m_contextPath + "/sources/" + lower;
2194             m_tree->remove(pathName);
2195             // ... and the peer-specific ones of *all* peers
2196             BOOST_FOREACH(const std::string peer,
2197                           m_tree->getChildren(m_contextPath + "/peers")) {
2198                 m_tree->remove(m_contextPath + "/peers/" + peer + "/sources/" + lower);
2199             }
2200         } else {
2201             // remove only inside the selected peer
2202             m_tree->remove(m_peerPath + "/sources/" + lower);
2203         }
2204     } else {
2205         // remove the peer-specific ones
2206         pathName = m_peerPath +
2207             (m_layout == SYNC4J_LAYOUT ? "spds/sources/" : "sources/") +
2208             lower;
2209         m_tree->remove(pathName);
2210     }
2211 }
2212
2213 void SyncConfig::clearSyncSourceProperties(const string &name)
2214 {
2215     SyncSourceNodes nodes = getSyncSourceNodes(name);
2216     setDefaultProps(SyncSourceConfig::getRegistry(),
2217                     nodes.getProperties(),
2218                     true,
2219                     !m_peerPath.empty(),
2220                     false);
2221 }
2222
2223 void SyncConfig::clearSyncProperties()
2224 {
2225     setDefaultProps(getRegistry(), getProperties(),
2226                     true,
2227                     !m_peerPath.empty(),
2228                     false);
2229 }
2230
2231 static void copyProperties(const ConfigNode &fromProps,
2232                            ConfigNode &toProps,
2233                            bool hidden,
2234                            bool unshared,
2235                            const ConfigPropertyRegistry &allProps)
2236 {
2237     BOOST_FOREACH(const ConfigProperty *prop, allProps) {
2238         if (prop->isHidden() == hidden &&
2239             (unshared ||
2240              prop->getSharing() != ConfigProperty::NO_SHARING)) {
2241             InitStateString value = prop->getProperty(fromProps);
2242             string name = prop->getName(toProps);
2243             toProps.setProperty(name,
2244                                 value,
2245                                 prop->getComment());
2246         }
2247     }
2248 }
2249
2250 static void copyProperties(const ConfigNode &fromProps,
2251                            ConfigNode &toProps)
2252 {
2253     ConfigProps props;
2254     fromProps.readProperties(props);
2255     toProps.writeProperties(props);
2256 }
2257
2258 void SyncConfig::copy(const SyncConfig &other,
2259                       const set<string> *sourceSet)
2260 {
2261     for (int i = 0; i < 2; i++ ) {
2262         boost::shared_ptr<const FilterConfigNode> fromSyncProps(other.getProperties(i));
2263         boost::shared_ptr<FilterConfigNode> toSyncProps(this->getProperties(i));
2264         copyProperties(*fromSyncProps,
2265                        *toSyncProps,
2266                        i,
2267                        !m_peerPath.empty(),
2268                        SyncConfig::getRegistry());
2269     }
2270
2271     list<string> sources;
2272     if (!sourceSet) {
2273         sources = other.getSyncSources();
2274     } else {
2275         BOOST_FOREACH(const string &sourceName, *sourceSet) {
2276             sources.push_back(sourceName);
2277         }
2278     }
2279     BOOST_FOREACH(const string &sourceName, sources) {
2280         ConstSyncSourceNodes fromNodes = other.getSyncSourceNodes(sourceName);
2281         SyncSourceNodes toNodes = this->getSyncSourceNodes(sourceName);
2282
2283         for (int i = 0; i < 2; i++ ) {
2284             copyProperties(*fromNodes.getProperties(i),
2285                            *toNodes.getProperties(i),
2286                            i,
2287                            !m_peerPath.empty(),
2288                            SyncSourceConfig::getRegistry());
2289         }
2290         copyProperties(*fromNodes.getTrackingNode(),
2291                        *toNodes.getTrackingNode());
2292         copyProperties(*fromNodes.getServerNode(),
2293                        *toNodes.getServerNode());
2294     }
2295 }
2296
2297 InitStateString SyncConfig::getSwv() const { return VERSION; }
2298 InitStateString SyncConfig::getDevType() const { return DEVICE_TYPE; }
2299
2300                      
2301 SyncSourceConfig::SyncSourceConfig(const string &name, const SyncSourceNodes &nodes) :
2302     m_name(name),
2303     m_nodes(nodes)
2304 {
2305 }
2306
2307 StringConfigProperty SyncSourceConfig::m_sourcePropSync("sync",
2308                                            "Requests a certain synchronization mode when initiating a sync:\n\n"
2309                                            "  two-way\n"
2310                                            "    only send/receive changes since last sync\n"
2311                                            "  slow\n"
2312                                            "    exchange all items\n"
2313                                            "  refresh-from-remote\n"
2314                                            "    discard all local items and replace with\n"
2315                                            "    the items on the peer\n"
2316                                            "  refresh-from-local\n"
2317                                            "    discard all items on the peer and replace\n"
2318                                            "    with the local items\n"
2319                                            "  one-way-from-remote\n"
2320                                            "    transmit changes from peer\n"
2321                                            "  one-way-from-local\n"
2322                                            "    transmit local changes\n"
2323                                            "  local-cache-slow (server only)\n"
2324                                            "    mirror remote data locally, transferring all data\n"
2325                                            "  local-cache-incremental (server only)\n"
2326                                            "    mirror remote data locally, transferring only changes;\n"
2327                                            "    falls back to local-cache-slow automatically if necessary\n"
2328                                            "  disabled (or none)\n"
2329                                            "    synchronization disabled\n"
2330                                            "\n"
2331                                            "refresh/one-way-from-server/client are also supported. Their use is\n"
2332                                            "discouraged because the direction of the data transfer depends\n"
2333                                            "on the role of the local side (can be server or client), which is\n"
2334                                            "not always obvious.\n"
2335                                            "\n"
2336                                            "When accepting a sync session in a SyncML server (HTTP server), only\n"
2337                                            "sources with sync != disabled are made available to the client,\n"
2338                                            "which chooses the final sync mode based on its own configuration.\n"
2339                                            "When accepting a sync session in a SyncML client (local sync with\n"
2340                                            "the server contacting SyncEvolution on a device), the sync mode\n"
2341                                            "specified in the client is typically overriden by the server.\n",
2342                                            "disabled",
2343                                            "",
2344                                            Values() +
2345                                            (Aliases("two-way")) +
2346                                            (Aliases("slow")) +
2347
2348                                            (Aliases("refresh-from-local")) +
2349                                            (Aliases("refresh-from-remote") + "refresh") +
2350                                            (Aliases("one-way-from-local")) +
2351                                            (Aliases("one-way-from-remote") + "one-way") +
2352
2353                                            (Aliases("refresh-from-client") + "refresh-client") +
2354                                            (Aliases("refresh-from-server") + "refresh-server") +
2355                                            (Aliases("one-way-from-client") + "one-way-client") +
2356                                            (Aliases("one-way-from-server") + "one-way-server") +
2357                                            (Aliases("local-cache-slow")) +
2358                                            (Aliases("local-cache-incremental") + "local-cache") +
2359                                            (Aliases("disabled") + "none"));
2360
2361 static class SourceBackendConfigProperty : public StringConfigProperty {
2362 public:
2363     SourceBackendConfigProperty() :
2364         StringConfigProperty("backend",
2365                              "Specifies the SyncEvolution backend and thus the\n"
2366                              "data which is synchronized by this source. Each\n"
2367                              "backend may support multiple databases (see 'database'\n"
2368                              "property), different formats inside that database (see\n"
2369                              "'databaseFormat'), and different formats when talking to\n"
2370                              "the sync peer (see 'syncFormat' and 'forceSyncFormat').\n"
2371                              "\n"
2372                              "A special 'virtual' backend combines several other\n"
2373                              "data sources and presents them as one set of items\n"
2374                              "to the peer. For example, Nokia phones typically\n"
2375                              "exchange tasks and events as part of one set of\n"
2376                              "calendar items.\n"
2377                              "\n"
2378                              "Right now such a virtual backend is limited to\n"
2379                              "combining one calendar source with events and one\n"
2380                              "task source. They have to be specified in the\n"
2381                              "``database`` property, typically like this:\n"
2382                              "``calendar,todo``\n"
2383                              "\n"
2384                              "Different sources combined in one virtual source must\n"
2385                              "have a common format. As with other backends,\n"
2386                              "the preferred format can be influenced via the 'syncFormat'\n"
2387                              "attribute.\n"
2388                              "\n"
2389                              "Here's the full list of potentially supported backends,\n"
2390                              "valid 'backend' values for each of them, and possible\n"
2391                              "formats. Note that SyncEvolution installations usually\n"
2392                              "support only a subset of the backends; that's why e.g.\n"
2393                              "\"addressbook\" is unambiguous although there are multiple\n"
2394                              "address book backends.\n"
2395                              "\n",
2396                              "select backend",
2397                              "",
2398                              Values() +
2399                              (Aliases("virtual")) +
2400                              (Aliases("calendar") + "events") +
2401                              (Aliases("addressbook") + "contacts") +
2402                              (Aliases("todo") + "tasks") +
2403                              (Aliases("memo") + "memos" + "notes"))
2404     {}
2405
2406     virtual string getComment() const {
2407         stringstream enabled, disabled;
2408         stringstream res;
2409
2410         SourceRegistry &registry(SyncSource::getSourceRegistry());
2411         BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
2412             static const std::string whitespace(" \t\n");
2413             string comment = boost::trim_right_copy_if(sourceInfos->m_typeDescr,
2414                                                        boost::is_any_of(whitespace));
2415             stringstream *curr = sourceInfos->m_enabled ? &enabled : &disabled;
2416             boost::replace_all(comment, "\n", "\n ");
2417             *curr << " " << comment << "\n";
2418         }
2419
2420         res << StringConfigProperty::getComment();
2421         if (enabled.str().size()) {
2422             res << "\n\nCurrently active::\n\n" << enabled.str();
2423         }
2424         if (disabled.str().size()) {
2425             res << "\n\nCurrently inactive::\n\n" << disabled.str();
2426         }
2427
2428         return boost::trim_right_copy(res.str());
2429     }
2430
2431     virtual Values getValues() const {
2432         Values res(StringConfigProperty::getValues());
2433
2434         const SourceRegistry &registry(SyncSource::getSourceRegistry());
2435         BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
2436             copy(sourceInfos->m_typeValues.begin(),
2437                  sourceInfos->m_typeValues.end(),
2438                  back_inserter(res));
2439         }
2440
2441         return res;
2442     }
2443 } sourcePropBackend;
2444
2445 StringConfigProperty sourcePropSyncFormat("syncFormat",
2446                                           "When there are alternative formats for the same data,\n"
2447                                           "each side of a sync offers all that it supports and marks one as\n"
2448                                           "preferred. If set, this property overrides the format\n"
2449                                           "that would normally be marked as preferred by a backend.\n"
2450                                           "\n"
2451                                           "Valid values depend on the backend. Here are some examples:\n"
2452                                           "  contacts - text/vcard = vCard 3.0 format\n"
2453                                           "             text/x-vcard = legacy vCard 2.1 format\n"
2454                                           "  calendar - text/calendar = iCalendar 2.0 format\n"
2455                                           "             text/x-vcalendar = legacy vCalendar 1.0 format\n"
2456                                           "\n"
2457                                           "Errors while starting to sync and parsing and/or storing\n"
2458                                           "items on either client or server can be caused by a mismatch between\n"
2459                                           "the sync format and uri at the peer.\n");
2460
2461 static BoolConfigProperty sourcePropForceSyncFormat("forceSyncFormat",
2462                                                     "Some peers get confused when offered multiple choices\n"
2463                                                     "for the sync format or pick the less optimal one.\n"
2464                                                     "In such a case, setting this property enforces that the\n"
2465                                                     "preferred format specified with 'syncFormat' is\n"
2466                                                     "really used.",
2467                                                     "FALSE");
2468
2469 static ConfigProperty sourcePropDatabaseID(Aliases("database") + "evolutionsource",
2470                                            "Picks one of the backend's databases:\n"
2471                                            "depending on the backend, one can set the name\n"
2472                                            "and/or a unique identifier.\n\n"
2473                                            "Most backends have a default database,\n"
2474                                            "like for example the system address book.\n"
2475                                            "Not setting this property selects that default\n"
2476                                            "database.\n\n"
2477                                            "If the backend is a virtual data source,\n"
2478                                            "this field must contain comma seperated list of\n"
2479                                            "sub datasources actually used to store data.\n"
2480                                            "If your sub datastore has a comma in name, you\n"
2481                                            "must prevent taht comma from being mistaken as the\n"
2482                                            "separator by preceding it with a backslash, like this:\n"
2483                                            "``database=Source1PartA\\,PartB,Source2\\\\Backslash``\n"
2484                                            "\n"
2485                                            "To get a full list of available databases,\n"
2486                                            "run ``syncevolution --print-databases``. The name\n"
2487                                            "is printed in front of the colon, followed by\n"
2488                                            "an identifier in brackets. Usually the name is unique and can be\n"
2489                                            "used to reference the data source. The default\n"
2490                                            "data source is marked with <default> at the end\n"
2491                                            "of the line, if there is a default.\n");
2492
2493 static StringConfigProperty sourcePropDatabaseFormat("databaseFormat",
2494                                                      "Defines the data format to be used by the backend for its\n"
2495                                                      "own storage. Typically backends only support one format\n"
2496                                                      "and ignore this property, but for example the file backend\n"
2497                                                      "uses it. See the 'backend' property for more information.\n");
2498
2499 static ConfigProperty sourcePropURI("uri",
2500                                     "this is appended to the server's URL to identify the\n"
2501                                     "server's database; if unset, the source name is used as\n"
2502                                     "fallback");
2503
2504 static ConfigProperty sourcePropUser(Aliases("databaseUser") + "evolutionuser",
2505                                      "authentication for backend data source; password can be specified\n"
2506                                      "in multiple ways, see SyncML server password for details\n"
2507                                      "\n"
2508                                      "Warning: setting database user/password in cases where it is not\n"
2509                                      "needed, as for example with local Evolution calendars and addressbooks,\n"
2510                                      "can cause the Evolution backend to hang.");
2511 static DatabasePasswordConfigProperty sourcePropPassword(Aliases("databasePassword") + "evolutionpassword", "","", "backend");
2512
2513 static ConfigProperty sourcePropAdminData(SourceAdminDataName,
2514                                           "used by the Synthesis library internally; do not modify");
2515
2516 static IntConfigProperty sourcePropSynthesisID("synthesisID", "unique integer ID, necessary for libsynthesis", "0");
2517
2518 /**
2519  * Same as RegisterSyncConfigProperties, only for SyncSource properties.
2520  */
2521 static class RegisterSyncSourceConfigProperties
2522 {
2523 public:
2524     RegisterSyncSourceConfigProperties()
2525     {
2526         ConfigPropertyRegistry &registry = SyncSourceConfig::getRegistry();
2527
2528         // temporarily move existing properties away so that the important
2529         // standard properties come first when using the traditional
2530         // push_back() way of adding them
2531         ConfigPropertyRegistry tmp;
2532         std::swap(registry, tmp);
2533
2534         registry.push_back(&SyncSourceConfig::m_sourcePropSync);
2535         registry.push_back(&sourcePropURI);
2536         registry.push_back(&sourcePropBackend);
2537         registry.push_back(&sourcePropSyncFormat);
2538         registry.push_back(&sourcePropForceSyncFormat);
2539         registry.push_back(&sourcePropDatabaseID);
2540         registry.push_back(&sourcePropDatabaseFormat);
2541         registry.push_back(&sourcePropUser);
2542         registry.push_back(&sourcePropPassword);
2543         registry.push_back(&sourcePropAdminData);
2544         registry.push_back(&sourcePropSynthesisID);
2545
2546         BOOST_FOREACH (const ConfigProperty *prop, tmp) {
2547             registry.push_back(prop);
2548         }
2549
2550         // obligatory source properties
2551         SyncSourceConfig::m_sourcePropSync.setObligatory(true);
2552
2553         // hidden source properties - only possible for
2554         // non-shared properties (other hidden nodes don't
2555         // exist at the moment)
2556         sourcePropAdminData.setHidden(true);
2557         sourcePropSynthesisID.setHidden(true);
2558
2559         // No global source properties. Does not make sense
2560         // conceptually.
2561
2562         // peer independent source properties
2563         sourcePropBackend.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2564         sourcePropDatabaseID.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2565         sourcePropDatabaseFormat.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2566         sourcePropUser.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2567         sourcePropPassword.setSharing(ConfigProperty::SOURCE_SET_SHARING);
2568     }
2569 } RegisterSyncSourceConfigProperties;
2570
2571
2572 ConfigPropertyRegistry &SyncSourceConfig::getRegistry()
2573 {
2574     static ConfigPropertyRegistry registry;
2575     return registry;
2576 }
2577
2578 SyncSourceNodes::SyncSourceNodes(bool havePeerNode,
2579                                  const boost::shared_ptr<FilterConfigNode> &sharedNode,
2580                                  const boost::shared_ptr<FilterConfigNode> &peerNode,
2581                                  const boost::shared_ptr<ConfigNode> &hiddenPeerNode,
2582                                  const boost::shared_ptr<ConfigNode> &trackingNode,
2583                                  const boost::shared_ptr<ConfigNode> &serverNode,
2584                                  const string &cacheDir) :
2585     m_havePeerNode(havePeerNode),
2586     m_sharedNode(sharedNode),
2587     m_peerNode(peerNode),
2588     m_hiddenPeerNode(hiddenPeerNode),
2589     m_trackingNode(trackingNode),
2590     m_serverNode(serverNode),
2591     m_cacheDir(cacheDir)
2592 {
2593     boost::shared_ptr<MultiplexConfigNode> mnode;
2594     mnode.reset(new MultiplexConfigNode(m_peerNode->getName(),
2595                                         SyncSourceConfig::getRegistry(),
2596                                         false));
2597     mnode->setHavePeerNodes(havePeerNode);
2598     m_props[false] = mnode;
2599     mnode->setNode(false, ConfigProperty::SOURCE_SET_SHARING,
2600                    m_sharedNode);
2601     mnode->setNode(false, ConfigProperty::NO_SHARING,
2602                    m_peerNode);
2603     // no multiplexing necessary for hidden peer properties yet
2604     m_props[true].reset(new FilterConfigNode(m_hiddenPeerNode));
2605 }
2606
2607
2608 boost::shared_ptr<FilterConfigNode>
2609 SyncSourceNodes::getNode(const ConfigProperty &prop) const
2610 {
2611     switch (prop.getSharing()) {
2612     case ConfigProperty::GLOBAL_SHARING:
2613         return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("no global source properties"))));
2614         break;
2615     case ConfigProperty::SOURCE_SET_SHARING:
2616         if (prop.isHidden()) {
2617             return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(boost::shared_ptr<ConfigNode>(new DevNullConfigNode("no hidden source set properties"))));
2618         } else {
2619             return m_sharedNode;
2620         }
2621         break;
2622     case ConfigProperty::NO_SHARING:
2623         if (prop.isHidden()) {
2624             return boost::shared_ptr<FilterConfigNode>(new FilterConfigNode(m_hiddenPeerNode));
2625         } else {
2626             return m_peerNode;
2627         }
2628         break;
2629     }
2630     return boost::shared_ptr<FilterConfigNode>();
2631 }
2632
2633 InitStateString SyncSourceConfig::getDatabaseID() const { return sourcePropDatabaseID.getProperty(*getNode(sourcePropDatabaseID)); }
2634 void SyncSourceConfig::setDatabaseID(const string &value, bool temporarily) { sourcePropDatabaseID.setProperty(*getNode(sourcePropDatabaseID), value, temporarily); }
2635 InitStateString SyncSourceConfig::getUser() const { return sourcePropUser.getProperty(*getNode(sourcePropUser)); }
2636 void SyncSourceConfig::setUser(const string &value, bool temporarily) { sourcePropUser.setProperty(*getNode(sourcePropUser), value, temporarily); }
2637 InitStateString SyncSourceConfig::getPassword() const {
2638     return sourcePropPassword.getCachedProperty(*getNode(sourcePropPassword), m_cachedPassword);
2639 }
2640 void SyncSourceConfig::checkPassword(UserInterface &ui, 
2641                                      const string &serverName, 
2642                                      FilterConfigNode& globalConfigNode) {
2643     sourcePropPassword.checkPassword(ui, serverName, globalConfigNode, m_name, getNode(sourcePropPassword));
2644 }
2645 void SyncSourceConfig::savePassword(UserInterface &ui, 
2646                                     const string &serverName, 
2647                                     FilterConfigNode& globalConfigNode) {
2648     sourcePropPassword.savePassword(ui, serverName, globalConfigNode, m_name, getNode(sourcePropPassword));
2649 }
2650 void SyncSourceConfig::setPassword(const string &value, bool temporarily) { m_cachedPassword = ""; sourcePropPassword.setProperty(*getNode(sourcePropPassword), value, temporarily); }
2651 InitStateString SyncSourceConfig::getURI() const { return sourcePropURI.getProperty(*getNode(sourcePropURI)); }
2652 InitStateString SyncSourceConfig::getURINonEmpty() const {
2653     InitStateString uri = sourcePropURI.getProperty(*getNode(sourcePropURI));
2654     if (uri.empty()) {
2655         uri = InitStateString(m_name, false);
2656     }
2657     return uri;
2658 }
2659 void SyncSourceConfig::setURI(const string &value, bool temporarily) { sourcePropURI.setProperty(*getNode(sourcePropURI), value, temporarily); }
2660 InitStateString SyncSourceConfig::getSync() const { return m_sourcePropSync.getProperty(*getNode(m_sourcePropSync)); }
2661 void SyncSourceConfig::setSync(const string &value, bool temporarily) { m_sourcePropSync.setProperty(*getNode(m_sourcePropSync), value, temporarily); }
2662
2663 SourceType::SourceType(const string &type)
2664 {
2665     m_forceFormat = false;
2666     size_t colon = type.find(':');
2667     if (colon != type.npos) {
2668         m_backend = type.substr(0, colon);
2669         sourcePropBackend.normalizeValue(m_backend);
2670         string format = type.substr(colon + 1);
2671         if (boost::ends_with(format, "!")) {
2672             m_forceFormat = true;
2673             format.resize(format.size() - 1);
2674         }
2675         colon = format.find(':');
2676         if (colon != format.npos) {
2677             // ignore obsolete Mime version
2678             m_format = format.substr(0, colon);
2679         } else {
2680             m_format = format;
2681         }
2682         // no difference between remote and local format
2683         m_localFormat = m_format;
2684     } else {
2685         m_backend = type;
2686     }
2687 }
2688
2689 string SourceType::toString() const
2690 {
2691     string type = m_backend;
2692     if (!m_format.empty()) {
2693         type += ":";
2694         type += m_format;
2695         if (m_forceFormat) {
2696             type += "!";
2697         }
2698     }
2699     return type;
2700 }
2701
2702 InitState<SourceType> SyncSourceConfig::getSourceType(const SyncSourceNodes &nodes)
2703 {
2704     // legacy "type" property is tried if the backend property is not set
2705     InitStateString backend = sourcePropBackend.getProperty(*nodes.getNode(sourcePropBackend));
2706     if (!backend.wasSet()) {
2707         string type;
2708         if (nodes.getNode(sourcePropBackend)->getProperty("type", type)) {
2709             return InitState<SourceType>(SourceType(type), true);
2710         }
2711     }
2712
2713     SourceType sourceType;
2714     sourceType.m_backend = backend;
2715     sourceType.m_localFormat = sourcePropDatabaseFormat.getProperty(*nodes.getNode(sourcePropDatabaseFormat));
2716     sourceType.m_format = sourcePropSyncFormat.getProperty(*nodes.getNode(sourcePropSyncFormat));
2717     sourceType.m_forceFormat = sourcePropForceSyncFormat.getPropertyValue(*nodes.getNode(sourcePropForceSyncFormat));
2718     return InitState<SourceType>(sourceType, backend.wasSet());
2719 }
2720 InitState<SourceType> SyncSourceConfig::getSourceType() const { return getSourceType(m_nodes); }
2721
2722 void SyncSourceConfig::setSourceType(const SourceType &type, bool temporarily)
2723 {
2724     // writing always uses the new properties: the config must have
2725     // been converted to the new format before writing is allowed
2726     setBackend(type.m_backend, temporarily);
2727     setDatabaseFormat(type.m_localFormat, temporarily);
2728     setSyncFormat(InitStateString(type.m_format, !type.m_format.empty()), temporarily);
2729     setForceSyncFormat(type.m_forceFormat, temporarily);
2730 }
2731
2732 void SyncSourceConfig::setBackend(const std::string &value, bool temporarily)
2733 {
2734     sourcePropBackend.setProperty(*getNode(sourcePropBackend),
2735                                   value,
2736                                   temporarily);    
2737 }
2738 InitStateString SyncSourceConfig::getBackend() const
2739 {
2740     return sourcePropBackend.getProperty(*getNode(sourcePropBackend));
2741 }
2742
2743 void SyncSourceConfig::setDatabaseFormat(const std::string &value, bool temporarily)
2744 {
2745     sourcePropDatabaseFormat.setProperty(*getNode(sourcePropDatabaseFormat),
2746                                          value,
2747                                          temporarily);
2748 }
2749 InitStateString SyncSourceConfig::getDatabaseFormat() const
2750 {
2751     return sourcePropDatabaseFormat.getProperty(*getNode(sourcePropDatabaseFormat));
2752 }
2753
2754 void SyncSourceConfig::setSyncFormat(const InitStateString &value, bool temporarily)
2755 {
2756     sourcePropSyncFormat.setProperty(*getNode(sourcePropSyncFormat),
2757                                      value,
2758                                      temporarily);
2759 }
2760 InitStateString SyncSourceConfig::getSyncFormat() const
2761 {
2762     return sourcePropSyncFormat.getProperty(*getNode(sourcePropSyncFormat));
2763 }
2764
2765 void SyncSourceConfig::setForceSyncFormat(bool value, bool temporarily)
2766 {
2767     sourcePropForceSyncFormat.setProperty(*getNode(sourcePropForceSyncFormat),
2768                                           value,
2769                                           temporarily);
2770 }
2771 InitState<bool> SyncSourceConfig::getForceSyncFormat() const
2772 {
2773     return sourcePropForceSyncFormat.getPropertyValue(*getNode(sourcePropForceSyncFormat));
2774 }
2775
2776 InitState<int> SyncSourceConfig::getSynthesisID() const {
2777     if (!m_synthesisID.wasSet()) {
2778         const_cast<InitState<int> &>(m_synthesisID) = sourcePropSynthesisID.getPropertyValue(*getNode(sourcePropSynthesisID));
2779     }
2780     return m_synthesisID;
2781 }
2782 void SyncSourceConfig::setSynthesisID(int value, bool temporarily) {
2783     m_synthesisID = value;
2784     sourcePropSynthesisID.setProperty(*getNode(sourcePropSynthesisID), value, temporarily);
2785 }
2786
2787 ConfigPasswordKey DatabasePasswordConfigProperty::getPasswordKey(const string &descr,
2788                                                                  const string &serverName,
2789                                                                  FilterConfigNode &globalConfigNode,
2790                                                                  const string &sourceName,
2791                                                                  const boost::shared_ptr<FilterConfigNode> &sourceConfigNode) const
2792 {
2793     ConfigPasswordKey key;
2794     key.user = sourcePropUser.getProperty(*sourceConfigNode);
2795     std::string configName = SyncConfig::normalizeConfigString(serverName, SyncConfig::NORMALIZE_LONG_FORMAT);
2796     std::string peer, context;
2797     SyncConfig::splitConfigString(configName, peer, context);
2798     key.object = "@";
2799     key.object += context;
2800     key.object += " ";
2801     key.object += sourceName;
2802     key.object += " backend";
2803     return key;
2804 }
2805
2806 // Used for built-in templates
2807 SyncConfig::TemplateDescription::TemplateDescription (const std::string &name, const std::string &description)
2808 :   m_templateId (name), m_description (description)
2809 {
2810     m_rank = TemplateConfig::LEVEL3_MATCH;
2811     m_deviceName = "";
2812     m_path = "";
2813     m_matchedModel = name;
2814 }
2815
2816 /* Ranking of template description is controled by the rank field, larger the
2817  * better
2818  */
2819 bool SyncConfig::TemplateDescription::compare_op (boost::shared_ptr<SyncConfig::TemplateDescription> &left, boost::shared_ptr<SyncConfig::TemplateDescription> &right)
2820 {
2821     //first sort against the fingerprint string
2822     if (left->m_deviceName != right->m_deviceName) {
2823         return (left->m_deviceName < right->m_deviceName);
2824     }
2825     // sort against the rank
2826     if (right->m_rank != left->m_rank) {
2827         return (right->m_rank < left->m_rank);
2828     }
2829     // sort against the template id, case-insensitive (for eGroupware < Funambol)
2830     return boost::ilexicographical_compare(left->m_templateId, right->m_templateId);
2831 }
2832
2833 TemplateConfig::TemplateConfig(const string &path) :
2834     m_template(new SingleFileConfigTree(path))
2835 {
2836     boost::shared_ptr<ConfigNode> metaNode = m_template->open("template.ini");
2837     metaNode->readProperties(m_metaProps);
2838 }
2839
2840 bool TemplateConfig::isTemplateConfig (const string &path) 
2841 {
2842     SingleFileConfigTree templ(path);
2843     boost::shared_ptr<ConfigNode> metaNode = templ.open("template.ini");
2844     if (!metaNode->exists()) {
2845         return false;
2846     }
2847     ConfigProps props;
2848     metaNode->readProperties(props);
2849     return !props.empty();
2850 }
2851
2852 bool TemplateConfig::isTemplateConfig() const
2853 {
2854     return !m_metaProps.empty();
2855 }
2856
2857 int TemplateConfig::serverModeMatch (SyncConfig::MatchMode mode)
2858 {
2859     if (mode != SyncConfig::MATCH_FOR_SERVER_MODE &&
2860         mode != SyncConfig::MATCH_FOR_CLIENT_MODE) {
2861         // no need to read config, peerIsClient doesn't matter
2862         // => fall back to BEST_MATCH directly
2863         return BEST_MATCH;
2864     }
2865
2866     boost::shared_ptr<ConfigNode> configNode = m_template->open("config.ini");
2867     std::string peerIsClient = configNode->readProperty ("peerIsClient");
2868     
2869     //not a match if serverMode does not match
2870     if ((peerIsClient.empty() || peerIsClient == "0") && mode == SyncConfig::MATCH_FOR_SERVER_MODE) {
2871         return NO_MATCH;
2872     }
2873     if (peerIsClient == "1" && mode == SyncConfig::MATCH_FOR_CLIENT_MODE){
2874         return NO_MATCH;
2875     }
2876     return BEST_MATCH;
2877 }
2878
2879 /**
2880  * The matching is based on Least common string algorithm,
2881  * with space, hyphen and underscore being treated as equal.
2882  * */
2883 int TemplateConfig::fingerprintMatch (const string &fingerprint)
2884 {
2885     //if input "", match all
2886     if (fingerprint.empty()) {
2887         return LEVEL3_MATCH;
2888     }
2889
2890     std::string fingerprintProp = m_metaProps["fingerprint"];
2891     std::vector <string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
2892     std::string input = fingerprint;
2893     boost::to_lower(input);
2894     boost::replace_all(input, " ", "_");
2895     boost::replace_all(input, "-", "_");
2896     //return the largest match value
2897     int max = NO_MATCH;
2898     BOOST_FOREACH (std::string sub, subfingerprints){
2899         std::vector< LCS::Entry <char> > result;
2900         std::string match = sub;
2901         boost::to_lower(match);
2902         boost::replace_all(match, " ", "_");
2903         boost::replace_all(match, "-", "_");
2904         LCS::lcs(match, input, std::back_inserter(result), LCS::accessor_sequence<std::string>());
2905         int score = result.size() *2 *BEST_MATCH /(sub.size() + fingerprint.size()) ;
2906         if (score > max) {
2907             max = score;
2908         }
2909     }
2910     return max;
2911 }
2912
2913 int TemplateConfig::metaMatch (const std::string &fingerprint, SyncConfig::MatchMode mode)
2914 {
2915     int serverMatch = serverModeMatch (mode);
2916     if (serverMatch == NO_MATCH){
2917         return NO_MATCH;
2918     }
2919     int fMatch = fingerprintMatch (fingerprint);
2920     return (serverMatch *1 + fMatch *3) >>2;
2921 }
2922
2923 string TemplateConfig::getDescription(){
2924     return m_metaProps["description"];
2925 }
2926
2927 string TemplateConfig::getFingerprint(){
2928     return m_metaProps["fingerprint"];
2929 }
2930
2931 string TemplateConfig::getTemplateName() {
2932     return m_metaProps["templateName"];
2933 }
2934
2935 /*
2936  * A unique identifier for this template, it must be unique and retrieveable.
2937  * We use the first entry in the "fingerprint" property for cmdline and
2938  * replace spaces with underscores, to make it more command line friendly.
2939  **/
2940 string TemplateConfig::getTemplateId(){
2941     if (m_id.empty()){
2942         std::string fingerprintProp = m_metaProps["fingerprint"];
2943         if (!fingerprintProp.empty()){
2944             std::vector<std::string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
2945             m_id = subfingerprints[0];
2946         }
2947         boost::replace_all(m_id, " ", "_");
2948     }
2949     return m_id;
2950 }
2951
2952 bool SecondsConfigProperty::checkValue(const string &value, string &error) const
2953 {
2954     unsigned int seconds;
2955     return parseDuration(value, error, seconds);
2956 }
2957
2958 InitState<unsigned int> SecondsConfigProperty::getPropertyValue(const ConfigNode &node) const
2959 {
2960     string name = getName(node);
2961     bool wasSet;
2962     std::string value = node.readProperty(name);
2963     if (value.empty()) {
2964         wasSet = false;
2965         value = getDefValue();
2966     } else {
2967         wasSet = true;
2968     }
2969     string error;
2970     unsigned int seconds;
2971     if (!parseDuration(value, error, seconds)) {
2972         throwValueError(node, name, value, error);
2973     }
2974     return InitState<unsigned int>(seconds, wasSet);
2975 }
2976
2977 bool SecondsConfigProperty::parseDuration(const string &value, string &error, unsigned int &seconds)
2978 {
2979     seconds = 0;
2980     if (value.empty()) {
2981         // ambiguous - zero seconds?!
2982         error = "duration expected, empty string not valid";
2983         return false;
2984     }
2985
2986     unsigned int current = 0;
2987     bool haveDigit = false;
2988     BOOST_FOREACH(char c, value) {
2989         if (isdigit(c)) {
2990             current = current * 10 + (c - '0');
2991             haveDigit = true;
2992         } else {
2993             unsigned int multiplier = 1;
2994             switch (toupper(c)) {
2995             case 'Y':
2996                 multiplier = 365 * 24 * 60 * 60;
2997                 break;
2998             case 'D':
2999                 multiplier = 24 * 60 * 60;
3000                 break;
3001             case 'H':
3002                 multiplier = 60 * 60;
3003                 break;
3004             case 'M':
3005                 multiplier = 60;
3006                 break;
3007             case 'S':
3008                 break;
3009             case ' ':
3010             case '\t':
3011                 continue;
3012             case '+':
3013                 break;
3014             default:
3015                 error = StringPrintf("invalid character '%c'", c);
3016                 return false;
3017             }
3018             if (!haveDigit && c != '+') {
3019                 error = StringPrintf("unit character without preceeding number: %c", c);
3020                 return false;
3021             }
3022             seconds += current * multiplier;
3023             current = 0;
3024             haveDigit = false;
3025         }
3026     }
3027     seconds += current;
3028     return true;
3029 }
3030
3031
3032 #ifdef ENABLE_UNIT_TESTS
3033
3034 class SyncConfigTest : public CppUnit::TestFixture {
3035     CPPUNIT_TEST_SUITE(SyncConfigTest);
3036     CPPUNIT_TEST(normalize);
3037     CPPUNIT_TEST(parseDuration);
3038     CPPUNIT_TEST(propertySpec);
3039     CPPUNIT_TEST_SUITE_END();
3040
3041 private:
3042     void normalize()
3043     {
3044         // use same dir as CmdlineTest...
3045         ScopedEnvChange xdg("XDG_CONFIG_HOME", "CmdlineTest");
3046         ScopedEnvChange home("HOME", "CmdlineTest");
3047
3048         rm_r("CmdlineTest");
3049
3050         CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3051                              SyncConfig::normalizeConfigString(""));
3052         CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3053                              SyncConfig::normalizeConfigString("@default"));
3054         CPPUNIT_ASSERT_EQUAL(std::string("@default"),
3055                              SyncConfig::normalizeConfigString("@DeFaULT"));
3056         CPPUNIT_ASSERT_EQUAL(std::string("foobar"),
3057                              SyncConfig::normalizeConfigString("FooBar"));
3058         CPPUNIT_ASSERT_EQUAL(std::string("foobar@something"),
3059                              SyncConfig::normalizeConfigString("FooBar@Something"));
3060         CPPUNIT_ASSERT_EQUAL(std::string("foo_bar_x_y_z"),
3061                              SyncConfig::normalizeConfigString("Foo/bar\\x:y:z"));
3062
3063         // keep @default if explicitly requested
3064         CPPUNIT_ASSERT_EQUAL(std::string("foobar@default"),
3065                              SyncConfig::normalizeConfigString("FooBar", SyncConfig::NORMALIZE_LONG_FORMAT));
3066
3067         // test config lookup
3068         SyncConfig foo_default("foo"), foo_other("foo@other"), bar("bar@other");
3069         foo_default.flush();
3070         foo_other.flush();
3071         bar.flush();
3072         CPPUNIT_ASSERT_EQUAL(std::string("foo"),
3073                              SyncConfig::normalizeConfigString("foo"));
3074         CPPUNIT_ASSERT_EQUAL(std::string("foo"),
3075                              SyncConfig::normalizeConfigString("foo@default"));
3076         CPPUNIT_ASSERT_EQUAL(std::string("foo@default"),
3077                              SyncConfig::normalizeConfigString("foo", SyncConfig::NORMALIZE_LONG_FORMAT));
3078         CPPUNIT_ASSERT_EQUAL(std::string("foo@default"),
3079                              SyncConfig::normalizeConfigString("foo@default", SyncConfig::NORMALIZE_LONG_FORMAT));
3080         CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3081                              SyncConfig::normalizeConfigString("foo@other"));
3082         foo_default.remove();
3083         CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3084                              SyncConfig::normalizeConfigString("foo"));
3085         CPPUNIT_ASSERT_EQUAL(std::string("foo@other"),
3086                              SyncConfig::normalizeConfigString("foo", SyncConfig::NORMALIZE_LONG_FORMAT));
3087     }
3088
3089     void parseDuration()
3090     {
3091         string error;
3092         unsigned int seconds;
3093         unsigned int expected;
3094
3095         CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("foo", error, seconds));
3096         CPPUNIT_ASSERT_EQUAL(error, string("invalid character 'f'"));
3097         CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("1g", error, seconds));
3098         CPPUNIT_ASSERT_EQUAL(error, string("invalid character 'g'"));
3099         CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("", error, seconds));
3100         CPPUNIT_ASSERT_EQUAL(error, string("duration expected, empty string not valid"));
3101
3102         expected = 5;
3103         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5", error, seconds));
3104         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3105         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("05", error, seconds));
3106         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3107         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("05s", error, seconds));
3108         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3109         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5s", error, seconds));
3110         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3111
3112         expected = (((1 * 365 + 2) * 24 + 3) * 60 + 4) * 60 + 5;
3113         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("1y2d3H4M5s", error, seconds));
3114         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3115         CPPUNIT_ASSERT(SecondsConfigProperty::parseDuration("5 + 1y+2d + 3 H4M", error, seconds));
3116         CPPUNIT_ASSERT_EQUAL(expected, seconds);
3117
3118         CPPUNIT_ASSERT(!SecondsConfigProperty::parseDuration("m", error, seconds));
3119     }
3120
3121     void propertySpec()
3122     {
3123         ScopedEnvChange xdg("XDG_CONFIG_HOME", "/dev/null");
3124         ScopedEnvChange home("HOME", "/dev/null");
3125         PropertySpecifier spec;
3126
3127         spec = PropertySpecifier::StringToPropSpec("foo");
3128         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3129         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3130         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3131         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.toString());
3132
3133         spec = PropertySpecifier::StringToPropSpec("source/foo@ContEXT");
3134         CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3135         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3136         CPPUNIT_ASSERT_EQUAL(string("@context"), spec.m_config);       
3137         CPPUNIT_ASSERT_EQUAL(string("source/foo@context"), spec.toString());
3138
3139         spec = PropertySpecifier::StringToPropSpec("source/foo@ContEXT", PropertySpecifier::NO_NORMALIZATION);
3140         CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3141         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3142         CPPUNIT_ASSERT_EQUAL(string("@ContEXT"), spec.m_config);       
3143         CPPUNIT_ASSERT_EQUAL(string("source/foo@ContEXT"), spec.toString());
3144
3145         spec = PropertySpecifier::StringToPropSpec("foo@peer@context");
3146         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3147         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3148         CPPUNIT_ASSERT_EQUAL(string("peer@context"), spec.m_config);
3149         CPPUNIT_ASSERT_EQUAL(string("foo@peer@context"), spec.toString());
3150
3151         spec = PropertySpecifier::StringToPropSpec("foo@context");
3152         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3153         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3154         CPPUNIT_ASSERT_EQUAL(string("@context"), spec.m_config);
3155         CPPUNIT_ASSERT_EQUAL(string("foo@context"), spec.toString());
3156
3157         spec = PropertySpecifier::StringToPropSpec("source/foo");
3158         CPPUNIT_ASSERT_EQUAL(string("source"), spec.m_source);
3159         CPPUNIT_ASSERT_EQUAL(string("foo"), spec.m_property);
3160         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3161         CPPUNIT_ASSERT_EQUAL(string("source/foo"), spec.toString());
3162
3163         spec = PropertySpecifier::StringToPropSpec("");
3164         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_source);
3165         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_property);
3166         CPPUNIT_ASSERT_EQUAL(string(""), spec.m_config);
3167         CPPUNIT_ASSERT_EQUAL(string(""), spec.toString());
3168     }
3169 };
3170
3171 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncConfigTest);
3172
3173 #endif // ENABLE_UNIT_TESTS
3174
3175 SE_END_CXX