Configuration templates matching: match templates based on metadata

Introduced TemplateConfig to abstracting the template configuration structure,
the template metadata used for matching is also parsed here.
The fields introduced in the metadata are:
PeerIsClient: identify whether this is a server side configuration or a client
side configuration.
Fingerprint: the matching string for this template, it is a comma separated string
with each string modeled as: "Manufacture_Model". The first substring is also
used as the name to identify this template so that user can select the template
by this name.
eg:
Nokia 7210c: Nokia_7210c
SyncEvolution server: SyncEvolutionServer, SyncEvolution
ScheduleWorld: ScheduleWorld,default
SyncEvolution client: SyncEvolutionClient, SyncEvolution

Description: this is a just a descriptive string not used for matching.

GetServerTemplates is changed to add another "devices" parameter to identify
it is asking for templates for a list of "devices". Each device is a tuple
<matchstring (devicename), matchMode (server/client/all)>.
TemplateList as the return type, which is a list of class TemplateDescription
so that we can also return enough information for corresponding templates. This
list is sorted by the 3-tuple <finger, rank, name>.

Add MatchServerTemplates method which will iterating all templates inside the
folder and match against the input parameter and finally return a sorted
list of matched templates.

The atcually fuzzy match algorithm is based on a LCS (added in the following
commit).

Cmdline interface is changed accordingly:
--template ? is changed to --template ?[string], so that user use the former
case to match all templates for a tradiontial SyncML client and the latter case
to match templates related to an input string.

SyncConfig API is also renamed (Server -> Peer) because both server/client
configuration/template are handled.

The original configuration template (Funambol and ScheduleWorld) has been moved
to the new template structure (under servers), they also have a .template.ini
file added so that they can be matched and picked up. All templates for
supported servers still have built-in template support in the code as before.
Templates for SyncEvolution based server is also added.

Server side templates are added (Nokia default, Nokia_7210c and SyncEvolutionServer).

Add unit test for the new template match use case.
This commit is contained in:
Chen Congwu 2010-01-19 15:01:05 +08:00 committed by Patrick Ohly
parent 7c48c8d121
commit d71aa7b2ac
62 changed files with 634 additions and 90 deletions

10
README
View File

@ -320,7 +320,7 @@ a list of valid values.
sources, so if you want to change a source property of just one specific
sync source, then use "--configure --source-property ... <server> <source>".
--template|-l <server name>|default|?
--template|-l <server name>|default|?<device>
Can be used to select from one of the built-in default configurations
for known SyncML servers. Defaults to the <server> name, so --template
only has to be specified when creating multiple different configurations
@ -330,6 +330,14 @@ a list of valid values.
Each template contains a pseudo-random device ID. Therefore setting the
"deviceId" sync property is only necessary when manually recreating a
configuration or when a more descriptive name is desired.
The available templates for different known SyncML servers are listed when
using a single question mark instead of template name. When using the
?<desireddevice> format, a fuzzy search for a template that might be
suitable for talking to such a device is done. The matching works best
when using <device> = <Manufacturer>_<Model>. If you don't know the
Manufacturer, you can just keep it as empty. The output in this mode
gives the template name followed by a short description and a rating how well
the template matches the device (higher is better).
--status|-t
The changes made to local data since the last synchronization are

View File

@ -188,6 +188,21 @@ nodist_client_test_SOURCES = ../test/test.cpp
CLIENT_LIB_TEST_FILES = \
testcases/lcs/file1.txt \
testcases/lcs/file2.txt \
testcases/templates/clients/default/.template.ini \
testcases/templates/clients/SyncEvolution/sources/addressbook/config.ini \
testcases/templates/clients/SyncEvolution/sources/memo/config.ini \
testcases/templates/clients/SyncEvolution/sources/todo/config.ini \
testcases/templates/clients/SyncEvolution/sources/calendar/config.ini \
testcases/templates/clients/SyncEvolution/.template.ini \
testcases/templates/clients/SyncEvolution/config.ini \
testcases/templates/clients/phone/nokia/default/.template.ini \
testcases/templates/clients/phone/nokia/S40/7210c/sources/addressbook/config.ini \
testcases/templates/clients/phone/nokia/S40/7210c/sources/memo/config.ini \
testcases/templates/clients/phone/nokia/S40/7210c/sources/todo/config.ini \
testcases/templates/clients/phone/nokia/S40/7210c/sources/calendar/config.ini \
testcases/templates/clients/phone/nokia/S40/7210c/sources/calendar+todo/config.ini \
testcases/templates/clients/phone/nokia/S40/7210c/.template.ini \
testcases/templates/clients/phone/nokia/S40/7210c/config.ini \
testcases/vcard21.vcf \
testcases/vcard30.vcf \
testcases/ical20.ics \
@ -247,7 +262,10 @@ testcase2patch: $(TEST_FILES_GENERATED)
done
# copy base test files
$(filter-out %.tem, $(filter testcases/%, $(subst $(srcdir)/../test/,,$(CLIENT_LIB_TEST_FILES)))) : % : $(srcdir)/../test/%
$(filter-out %.tem testcases/templates/%, $(filter testcases/%, $(subst $(srcdir)/../test/,,$(CLIENT_LIB_TEST_FILES)))) : % : $(srcdir)/../test/%
mkdir -p ${@D}
cp $< $@
$(filter testcases/templates/%, $(CLIENT_LIB_TEST_FILES)) : testcases/% : $(srcdir)/%
mkdir -p ${@D}
cp $< $@

View File

@ -215,7 +215,7 @@ public:
// device ID is different
config.reset(new SyncConfig(string(server) + "_" + id + "@client-test-" + id));
config->setDefaults();
from = SyncConfig::createServerTemplate(server);
from = SyncConfig::createPeerTemplate(server);
if(from) {
set<string> filter;
config->copy(*from, &filter);

View File

@ -1398,14 +1398,18 @@ ReadOperations::ReadOperations(const std::string &config_name) :
void ReadOperations::getConfigs(bool getTemplates, std::vector<std::string> &configNames)
{
SyncConfig::ServerList list;
if (getTemplates) {
list = SyncConfig::getServerTemplates();
SyncConfig::DeviceList devices;
SyncConfig::TemplateList list = SyncConfig::getPeerTemplates(devices);
BOOST_FOREACH(const boost::shared_ptr<SyncConfig::TemplateDescription> server, list) {
configNames.push_back(server->m_name);
//TODO create the template filters
}
} else {
list = SyncConfig::getServers();
}
BOOST_FOREACH(const SyncConfig::ServerList::value_type &server, list) {
configNames.push_back(server.first);
SyncConfig::ConfigList list = SyncConfig::getConfigs();
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) {
configNames.push_back(server.first);
}
}
}
@ -1440,7 +1444,7 @@ void ReadOperations::getConfig(bool getTemplate,
SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_configName),
peer, context);
syncConfig = SyncConfig::createServerTemplate(peer);
syncConfig = SyncConfig::createPeerTemplate(peer);
if(!syncConfig.get()) {
SE_THROW_EXCEPTION(NoSuchConfig, "No template '" + m_configName + "' found");
}
@ -2666,8 +2670,8 @@ void Connection::process(const Caller_t &caller,
// same serverID ("PC Suite"), so check properties of the
// of our configs first before going back to the name itself.
std::string serverID = san.fServerID;
SyncConfig::ServerList servers = SyncConfig::getServers();
BOOST_FOREACH(const SyncConfig::ServerList::value_type &server,
SyncConfig::ConfigList servers = SyncConfig::getConfigs();
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,
servers) {
SyncConfig conf(server.first);
if (conf.getSyncURL() == serverID) {
@ -2682,12 +2686,13 @@ void Connection::process(const Caller_t &caller,
if (trans != m_peer.end() && id != m_peer.end()) {
if (trans->second == "org.openobex.obexd") {
string btAddr = id->second.substr(0, id->second.find("+"));
BOOST_FOREACH(const SyncConfig::ServerList::value_type &server,
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,
servers) {
SyncConfig conf(server.first);
string url = conf.getSyncURL();
url = url.substr (0, url.find("+"));
SE_LOG_DEBUG (NULL, NULL, "matching against %s",url.c_str());
//TODO working with multiple SyncURLs
if (url.find ("obex-bt://") ==0 && url.substr(strlen("obex-bt://"), url.npos) == btAddr) {
config = server.first;
break;
@ -2697,7 +2702,7 @@ void Connection::process(const Caller_t &caller,
}
if (config.empty()) {
BOOST_FOREACH(const SyncConfig::ServerList::value_type &server,
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,
servers) {
if (server.first == serverID) {
config = serverID;
@ -2799,8 +2804,8 @@ void Connection::process(const Caller_t &caller,
// TODO: proper exception
throw runtime_error("could not extract LocURI=deviceID from initial message");
}
BOOST_FOREACH(const StringPair &entry,
SyncConfig::getServers()) {
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &entry,
SyncConfig::getConfigs()) {
SyncConfig peer(entry.first);
if (info.m_deviceID == peer.getRemoteDevID()) {
config = entry.first;

View File

@ -89,7 +89,7 @@ bool Cmdline::parse()
m_argv[opt - 1], opt == m_argc ? NULL : m_argv[opt])) {
return false;
}
} else if(boost::iequals(m_argv[opt], "--template") ||
}else if(boost::iequals(m_argv[opt], "--template") ||
boost::iequals(m_argv[opt], "-l")) {
opt++;
if (opt >= m_argc) {
@ -98,10 +98,11 @@ bool Cmdline::parse()
}
m_template = m_argv[opt];
m_configure = true;
if (boost::trim_copy(m_template) == "?") {
dumpServers("Available configuration templates:",
SyncConfig::getServerTemplates());
string temp = boost::trim_copy (m_template);
if (temp.find ("?") == 0){
m_printTemplates = true;
m_dontrun = true;
m_template = temp.substr (1);
}
} else if(boost::iequals(m_argv[opt], "--print-servers")) {
m_printServers = true;
@ -185,8 +186,19 @@ bool Cmdline::run() {
printf("%s", EDSAbiWrapperInfo());
printf("%s", SyncSource::backendsInfo().c_str());
} else if (m_printServers || boost::trim_copy(m_server) == "?") {
dumpServers("Configured servers:",
SyncConfig::getServers());
dumpConfigs("Configured servers:",
SyncConfig::getConfigs());
} else if (m_printTemplates) {
SyncConfig::DeviceList devices;
if (m_template.empty()){
dumpConfigTemplates("Available configuration templates:",
SyncConfig::getPeerTemplates(devices), false);
} else {
//limiting at templates for syncml clients only.
devices.push_back (SyncConfig::DeviceList::value_type (m_template, SyncConfig::MATCH_FOR_SERVER_MODE));
dumpConfigTemplates("Available configuration templates:",
SyncConfig::matchPeerTemplates(devices), true);
}
} else if (m_dontrun) {
// user asked for information
} else if (m_argc == 1) {
@ -236,7 +248,7 @@ bool Cmdline::run() {
string peer, context;
SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(m_template), peer, context);
config = SyncConfig::createServerTemplate(peer);
config = SyncConfig::createPeerTemplate(peer);
if (!config.get()) {
m_err << "ERROR: no configuration template for '" << m_template << "' available." << endl;
return false;
@ -340,11 +352,11 @@ bool Cmdline::run() {
fromScratch = true;
string configTemplate = m_template.empty() ? m_server : m_template;
SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configTemplate), peer, context);
from = SyncConfig::createServerTemplate(peer);
from = SyncConfig::createPeerTemplate(peer);
if (!from.get()) {
m_err << "ERROR: no configuration template for '" << configTemplate << "' available." << endl;
dumpServers("Available configuration templates:",
SyncConfig::getServerTemplates());
dumpConfigTemplates("Available configuration templates:",
SyncConfig::getPeerTemplates(SyncConfig::DeviceList()));
return false;
}
@ -762,18 +774,35 @@ void Cmdline::listSources(SyncSource &syncSource, const string &header)
}
}
void Cmdline::dumpServers(const string &preamble,
const SyncConfig::ServerList &servers)
void Cmdline::dumpConfigs(const string &preamble,
const SyncConfig::ConfigList &servers)
{
m_out << preamble << endl;
BOOST_FOREACH(const SyncConfig::ServerList::value_type &server,servers) {
m_out << " " << server.first << " = " << server.second << endl;
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server,servers) {
m_out << " " << server.first << " = " << server.second <<endl;
}
if (!servers.size()) {
m_out << " none" << endl;
}
}
void Cmdline::dumpConfigTemplates(const string &preamble,
const SyncConfig::TemplateList &templates,
bool printRank)
{
m_out << preamble << endl;
BOOST_FOREACH(const SyncConfig::TemplateList::value_type server,templates) {
m_out << " " << server->m_name << " = " << server->m_description;
if (printRank){
m_out << " " << server->m_rank;
}
m_out << endl;
}
if (!templates.size()) {
m_out << " none" << endl;
}
}
void Cmdline::dumpProperties(const ConfigNode &configuredProps,
const ConfigPropertyRegistry &allProps)
{
@ -1234,6 +1263,7 @@ class CmdlineTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testPrintConfig);
CPPUNIT_TEST(testPrintFileTemplates);
CPPUNIT_TEST(testTemplate);
CPPUNIT_TEST(testMatchTemplate);
CPPUNIT_TEST(testAddSource);
CPPUNIT_TEST(testSync);
CPPUNIT_TEST(testConfigure);
@ -1544,12 +1574,54 @@ protected:
" Mobical = http://www.mobical.net\n"
" Oracle = http://www.oracle.com/technology/products/beehive/index.html\n"
" ScheduleWorld = http://www.scheduleworld.com\n"
" SyncEvolution = http://www.syncevolution.org\n"
" Synthesis = http://www.synthesis.ch\n"
" ZYB = http://www.zyb.com\n",
help.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help.m_err.str());
}
void testMatchTemplate() {
ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "testcases/templates");
TestCmdline help1("--template", "?nokia_7210c", NULL);
help1.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
" Nokia_7210c = Nokia 7210c phone 5\n"
" Nokia = Default templates for nokia phone 3\n"
" SyncEvolutionClient = SyncEvolution server side template 2\n"
" ServerDefault = server side default template 1\n",
help1.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help1.m_err.str());
TestCmdline help2("--template", "?nokia", NULL);
help2.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
" Nokia = Default templates for nokia phone 5\n"
" Nokia_7210c = Nokia 7210c phone 3\n"
" SyncEvolutionClient = SyncEvolution server side template 2\n"
" ServerDefault = server side default template 1\n",
help2.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help2.m_err.str());
TestCmdline help3("--template", "?7210c", NULL);
help3.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
" Nokia_7210c = Nokia 7210c phone 3\n"
" Nokia = Default templates for nokia phone 1\n"
" ServerDefault = server side default template 1\n"
" SyncEvolutionClient = SyncEvolution server side template 1\n",
help3.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help3.m_err.str());
TestCmdline help4("--template", "?syncevolution", NULL);
help4.doit();
CPPUNIT_ASSERT_EQUAL_DIFF("Available configuration templates:\n"
" SyncEvolutionClient = SyncEvolution server side template 5\n"
" Nokia = Default templates for nokia phone 2\n"
" Nokia_7210c = Nokia 7210c phone 2\n"
" ServerDefault = server side default template 2\n",
help4.m_out.str());
CPPUNIT_ASSERT_EQUAL_DIFF("", help4.m_err.str());
}
void testPrintServers() {
ScopedEnvChange templates("SYNCEVOLUTION_TEMPLATE_DIR", "/dev/null");
ScopedEnvChange xdg("XDG_CONFIG_HOME", m_testDir);

View File

@ -76,6 +76,7 @@ protected:
Bool m_run;
Bool m_migrate;
Bool m_printServers;
Bool m_printTemplates;
Bool m_printConfig;
Bool m_printSessions;
Bool m_dontrun;
@ -135,8 +136,12 @@ protected:
*/
void listSources(SyncSource &syncSource, const string &header);
void dumpServers(const string &preamble,
const SyncConfig::ServerList &servers);
void dumpConfigs(const string &preamble,
const SyncConfig::ConfigList &servers);
void dumpConfigTemplates(const string &preamble,
const SyncConfig::TemplateList &templates,
bool printRank = false);
void dumpProperties(const ConfigNode &configuredProps,
const ConfigPropertyRegistry &allProps);

View File

@ -28,12 +28,14 @@
#include <syncevo/VolatileConfigNode.h>
#include <syncevo/DevNullConfigNode.h>
#include <syncevo/MultiplexConfigNode.h>
#include <syncevo/lcs.h>
#include <synthesis/timeutil.h>
#include <boost/foreach.hpp>
#include <iterator>
#include <algorithm>
#include <functional>
#include <queue>
#include <unistd.h>
#include "config.h"
@ -151,7 +153,7 @@ SyncConfig::SyncConfig(const string &peer,
// when ignoring their context. Peer list is sorted by name,
// therefore shorter config names (= without context) are
// found first, as intended.
BOOST_FOREACH(const StringPair &entry, getServers()) {
BOOST_FOREACH(const StringPair &entry, getConfigs()) {
string entry_peer, entry_context;
splitConfigString(entry.first, entry_peer, entry_context);
if (m_peer == entry_peer) {
@ -310,7 +312,7 @@ string SyncConfig::getRootPath() const
void SyncConfig::addPeers(const string &root,
const std::string &configname,
SyncConfig::ServerList &res) {
SyncConfig::ConfigList &res) {
FileConfigTree tree(root, "", false);
list<string> servers = tree.getChildren("");
BOOST_FOREACH(const string &server, servers) {
@ -328,18 +330,18 @@ void SyncConfig::addPeers(const string &root,
if (!access((root + "/" + peerPath).c_str(), F_OK)) {
// not a real HTTP server, search for peers
BOOST_FOREACH(const string &peer, tree.getChildren(peerPath)) {
res.push_back(pair<string, string>(normalizeConfigString(peer + "@" + server),
root + "/" + peerPath + "/" + peer));
res.push_back(pair<string, string> (normalizeConfigString(peer + "@" + server),
root + "/" + peerPath + "/" + peer));
}
} else if (!access((root + "/" + server + "/" + configname).c_str(), F_OK)) {
res.push_back(pair<string, string>(server, root + "/" + server));
res.push_back(pair<string, string> (server, root + "/" + server));
}
}
}
SyncConfig::ServerList SyncConfig::getServers()
SyncConfig::ConfigList SyncConfig::getConfigs()
{
ServerList res;
ConfigList res;
addPeers(getOldRoot(), "config.txt", res);
addPeers(getNewRoot(), "config.ini", res);
@ -350,36 +352,32 @@ SyncConfig::ServerList SyncConfig::getServers()
return res;
}
SyncConfig::ServerList SyncConfig::getServerTemplates()
/* Get a list of all templates, both for any phones listed in @peers*/
SyncConfig::TemplateList SyncConfig::getPeerTemplates(const DeviceList &peers)
{
class TmpList : public ServerList {
public:
void addDefaultTemplate(const string &server, const string &url) {
BOOST_FOREACH(const value_type &entry, static_cast<ServerList &>(*this)) {
if (boost::iequals(entry.first, server)) {
// already present
return;
}
}
push_back(value_type(server, url));
}
} result;
TemplateList result1, result2;
result1 = matchPeerTemplates (peers);
result2 = getBuiltInTemplates();
// scan TEMPLATE_DIR for templates
string templateDir(TEMPLATE_DIR);
if (isDir(templateDir)) {
ReadDir dir(templateDir);
BOOST_FOREACH(const string &entry, dir) {
if (isDir(templateDir + "/" + entry)) {
boost::shared_ptr<SyncConfig> config = SyncConfig::createServerTemplate(entry);
string comment = config->getWebURL();
if (comment.empty()) {
comment = templateDir + "/" + entry;
result1.insert (result1.end(), result2.begin(), result2.end());
return result1;
}
SyncConfig::TemplateList SyncConfig::getBuiltInTemplates()
{
class TmpList : public TemplateList {
public:
void addDefaultTemplate(const string &server, const string &url) {
BOOST_FOREACH(const boost::shared_ptr<TemplateDescription> entry, static_cast<TemplateList &>(*this)) {
if (boost::iequals(entry->m_name, server)) {
//already present
return;
}
}
result.push_back(ServerList::value_type(entry, comment));
push_back (boost::shared_ptr<TemplateDescription> (new TemplateDescription(server, url)));
}
}
}
} result;
// builtin templates if not present
result.addDefaultTemplate("Funambol", "http://my.funambol.com");
@ -391,12 +389,62 @@ SyncConfig::ServerList SyncConfig::getServerTemplates()
result.addDefaultTemplate("Mobical", "http://www.mobical.net");
result.addDefaultTemplate("Oracle", "http://www.oracle.com/technology/products/beehive/index.html");
result.addDefaultTemplate("Goosync", "http://www.goosync.com/");
result.addDefaultTemplate("SyncEvolution", "http://www.syncevolution.org");
result.sort();
result.sort (TemplateDescription::compare_op);
return result;
}
boost::shared_ptr<SyncConfig> SyncConfig::createServerTemplate(const string &server)
static string SyncEvolutionTemplateDir()
{
string templateDir(TEMPLATE_DIR);
const char *envvar = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
if (envvar) {
templateDir = envvar;
}
return templateDir;
}
SyncConfig::TemplateList SyncConfig::matchPeerTemplates(const DeviceList &peers)
{
TemplateList result;
// match against all possible templates without any assumption on directory
// layout, the match is entirely based on the metadata .template.ini
string templateDir(SyncEvolutionTemplateDir());
std::queue <std::string, std::list<std::string> > directories;
if (isDir(templateDir)) {
directories.push (templateDir);
}
while (!directories.empty()) {
string sDir = directories.front();
directories.pop();
if (!TemplateConfig::isTemplateConfig(sDir)) {
ReadDir dir(sDir);
//not a template folder, check all sub directories
BOOST_FOREACH(const string &entry, dir) {
if (isDir(sDir + "/" + entry)) {
directories.push (sDir + "/" + entry);
}
}
} else {
TemplateConfig templateConf (sDir);
BOOST_FOREACH (const DeviceList::value_type &entry, peers){
int rank = templateConf.metaMatch (entry.first, entry.second);
if (rank > TemplateConfig::NO_MATCH) {
result.push_back (boost::shared_ptr<TemplateDescription>(
new TemplateDescription(templateConf.getName(),
templateConf.getDescription(), rank, entry.first, sDir, templateConf.getFingerprint())));
}
}
}
}
result.sort (TemplateDescription::compare_op);
return result;
}
boost::shared_ptr<SyncConfig> SyncConfig::createPeerTemplate(const string &server)
{
if (server.empty()) {
// Empty template name => no such template. This check is
@ -407,24 +455,49 @@ boost::shared_ptr<SyncConfig> SyncConfig::createServerTemplate(const string &ser
}
// case insensitive search for read-only file template config
string templateConfig(TEMPLATE_DIR);
const char *envvar = getenv("SYNCEVOLUTION_TEMPLATE_DIR");
if (envvar) {
templateConfig = envvar;
}
if (isDir(templateConfig)) {
ReadDir dir(templateConfig);
templateConfig = dir.find(boost::iequals(server, "default") ?
string("ScheduleWorld") : server,
false);
} else {
templateConfig = "";
}
string templateConfig(SyncEvolutionTemplateDir());
if (templateConfig.empty()) {
// not found, avoid reading current directory by using one which doesn't exist
templateConfig = "/dev/null";
// before starting another fuzzy match process, first try to load the
// template directly taking the parameter as the path
if (isDir (server) && TemplateConfig::isTemplateConfig(server)) {
templateConfig = server;
} else {
std::queue <std::string, std::list<std::string> > directories;
if (isDir(templateConfig)) {
directories.push (templateConfig);
}
templateConfig = "";
int maxmatch = TemplateConfig::NO_MATCH;
while (!directories.empty()) {
string sDir = directories.front();
directories.pop();
if (!TemplateConfig::isTemplateConfig(sDir)) {
ReadDir dir(sDir);
//not a template folder, check all sub directories
BOOST_FOREACH(const string &entry, dir) {
if (isDir(sDir + "/" + entry)) {
directories.push (sDir + "/" + entry);
}
}
} else {
TemplateConfig templateConf (sDir);
int rank = templateConf.metaMatch (server, MATCH_ALL);
if (rank > maxmatch){
maxmatch = rank;
templateConfig = sDir;
if (maxmatch == TemplateConfig::BEST_MATCH){
break;
}
}
}
}
if (templateConfig.empty()) {
// not found, avoid reading current directory by using one which doesn't exist
templateConfig = "/dev/null";
}
}
boost::shared_ptr<FileConfigTree> tree(new FileConfigTree(templateConfig, "", false));
tree->setReadOnly(true);
boost::shared_ptr<SyncConfig> config(new SyncConfig(server, tree));
@ -633,6 +706,18 @@ boost::shared_ptr<SyncConfig> SyncConfig::createServerTemplate(const string &ser
source->setURI("tasks");
source = config->getSyncSourceConfig("memo");
source->setURI("");
} else if (boost::iequals(server, "syncevolution")) {
config->setSyncURL("http://yourserver:port");
config->setWebURL("http://www.syncevolution.org");
config->setConsumerReady(false);
source = config->getSyncSourceConfig("addressbook");
source->setURI("addressbook");
source = config->getSyncSourceConfig("calendar");
source->setURI("calendar");
source = config->getSyncSourceConfig("todo");
source->setURI("todo");
source = config->getSyncSourceConfig("memo");
source->setURI("memo");
} else {
config.reset();
}
@ -1911,5 +1996,121 @@ ConfigPasswordKey EvolutionPasswordConfigProperty::getPasswordKey(const string &
return key;
}
// Used for built-in templates
SyncConfig::TemplateDescription::TemplateDescription (const std::string &name, const std::string &description)
: m_name (name), m_description (description)
{
m_rank = TemplateConfig::LEVEL3_MATCH;
m_fingerprint = "";
m_path = "";
m_matchedModel = name;
}
/* Ranking of template description is controled by the rank field, larger the
* better
*/
bool SyncConfig::TemplateDescription::compare_op (boost::shared_ptr<SyncConfig::TemplateDescription> &left, boost::shared_ptr<SyncConfig::TemplateDescription> &right)
{
//first sort against the fingerprint string
if (left->m_fingerprint != right->m_fingerprint) {
return (left->m_fingerprint < right->m_fingerprint);
}
// sort against the rank
if (right->m_rank != left->m_rank) {
return (right->m_rank < left->m_rank);
}
// sort against the config name
return (left->m_name < right->m_name);
}
TemplateConfig::TemplateConfig (const string &path)
: m_metaNode (new FileConfigNode (path, ".template.ini", true)),
m_name("")
{
m_metaNode->readProperties(m_metaProps);
}
bool TemplateConfig::isTemplateConfig (const string &dir)
{
return !ReadDir(dir).find (".template.ini", false).empty();
}
int TemplateConfig::serverModeMatch (SyncConfig::MatchMode mode)
{
std::string peerIsClient = m_metaProps["peerIsClient"];
//not a match if serverMode does not match
if ((peerIsClient.empty() || peerIsClient == "0") && mode == SyncConfig::MATCH_FOR_SERVER_MODE) {
return NO_MATCH;
}
if (peerIsClient == "1" && mode == SyncConfig::MATCH_FOR_CLIENT_MODE){
return NO_MATCH;
}
return BEST_MATCH;
}
/**
* The matching is based on Least common string algorithm
* */
int TemplateConfig::fingerprintMatch (const string &fingerprint)
{
//if input "", match all
if (fingerprint.empty()) {
return LEVEL3_MATCH;
}
std::string fingerprintProp = m_metaProps["fingerprint"];
std::vector <string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
std::string input = fingerprint;
boost::to_lower(input);
//return the largest match value
int max = NO_MATCH;
BOOST_FOREACH (std::string sub, subfingerprints){
if (boost::iequals (sub, "default")){
if (LEVEL1_MATCH > max) {
max = LEVEL1_MATCH;
}
continue;
}
std::vector< LCS::Entry <char> > result;
std::string match = sub;
boost::to_lower(match);
LCS::lcs(match, input, std::back_inserter(result), LCS::accessor_sequence<std::string>());
int score = result.size() *2 *BEST_MATCH /(sub.size() + fingerprint.size()) ;
if (score > max) {
max = score;
}
}
return max;
}
int TemplateConfig::metaMatch (const std::string &fingerprint, SyncConfig::MatchMode mode)
{
int serverMatch = serverModeMatch (mode);
if (serverMatch == NO_MATCH){
return NO_MATCH;
}
int fMatch = fingerprintMatch (fingerprint);
return (serverMatch *1 + fMatch *3) >>2;
}
string TemplateConfig::getDescription(){
return m_metaProps["description"];
}
string TemplateConfig::getFingerprint(){
return m_metaProps["fingerprint"];
}
string TemplateConfig::getName(){
if (m_name.empty()){
std::string fingerprintProp = m_metaProps["fingerprint"];
if (!fingerprintProp.empty()){
std::vector<std::string> subfingerprints = unescapeJoinedString (fingerprintProp, ',');
m_name = subfingerprints[0];
}
}
return m_name;
}
SE_END_CXX

View File

@ -22,6 +22,7 @@
#include <syncevo/FilterConfigNode.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/FileConfigNode.h>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string/predicate.hpp>
@ -836,29 +837,101 @@ class SyncConfig {
/** absolute directory name of the configuration root */
string getRootPath() const;
typedef list< std::pair<std::string, std::string> > ServerList;
typedef list< std::pair<std::string, std::string> > ConfigList;
/** A simple description of the template or the configuration based on a
* template. The rank field is used to indicate how good it matches the
* user input <MacAddress, DeviceName> */
struct TemplateDescription {
// The name of the template
std::string m_name;
// The description of the template (eg. the web server URL for a
// SyncML server. This is not used for UI, only CMD line used this.
std::string m_description;
// The matched percentage of the template, larger the better.
int m_rank;
// A string identify which fingerprint the template is matched with.
std::string m_fingerprint;
// A unique string identify the template path, so that a later operation
// fetching this config will be much easier
std::string m_path;
// A string indicates the original fingerprint in the matched template, this
// will not necessarily the same with m_fingerprint
std::string m_matchedModel;
TemplateDescription (const std::string &name, const std::string &description,
const int rank, const std::string &fingerprint, const std::string &path, const std::string &model)
: m_name (name),
m_description (description),
m_rank (rank),
m_fingerprint (fingerprint),
m_path (path),
m_matchedModel(model)
{
}
TemplateDescription (const std::string &name, const std::string &description);
static bool compare_op (boost::shared_ptr<TemplateDescription> &left, boost::shared_ptr<TemplateDescription> &right);
};
enum MatchMode {
/*Match templates when we work as SyncML server, i.e. the peer is the client*/
MATCH_FOR_SERVER_MODE,
/*Match templates when work as SyncML client, i.e. the peer is the server*/
MATCH_FOR_CLIENT_MODE,
/*Match templates for both SyncML server and SyncML client*/
MATCH_ALL,
INVALID
};
typedef list<boost::shared_ptr <TemplateDescription> > TemplateList;
typedef list<std::pair <std::string, SyncConfig::MatchMode> > DeviceList;
/**
* returns list of servers in either the old (.sync4j) or
* new config directory (.config), given as server name
* and absolute root of config
*/
static ServerList getServers();
static ConfigList getConfigs();
/**
* returns list of available config templates
* returns list of available config templates:
* for each peer listed in @peers, matching against the fingerprint information
* from the peer (deviceName likely), sorted by the matching score,
* templates failed to match(as long as it's for SyncML server) will also
* be returned as a fallback mechanism so that user can select a configuration
* template manually.
* Any templates for SyncMl Client is also returned, with a default rank.
* The assumption currently is only work for SyncML client peers.
* DeviceList is a list of matching tuples <fingerprint, SyncConfig::MatchMode>.
*/
static ServerList getServerTemplates();
static TemplateList getPeerTemplates(const DeviceList &peers);
/**
* match the built-in templates against @param fingerprint, return a list of
* servers sorted by the matching rank.
* */
static TemplateList matchPeerTemplates(const DeviceList &peers);
/**
* get the built-in default templates
*/
static TemplateList getBuiltInTemplates ();
/**
* Creates a new instance of a configuration template.
* The result can be modified to set filters, but it
* cannot be flushed.
*
* @param peer a configuration name, *without* a context (scheduleworld, not scheduleworld@default)
* @param peer a configuration name, *without* a context (scheduleworld, not scheduleworld@default),
* or a configuration path in the system directory which can avoid another fuzzy match process.
* @return NULL if no such template
*/
static boost::shared_ptr<SyncConfig> createServerTemplate(const string &peer);
static boost::shared_ptr<SyncConfig> createPeerTemplate(const string &peer);
/** true if the main configuration file already exists */
bool exists() const;
@ -1219,7 +1292,7 @@ private:
*/
static void addPeers(const string &root,
const std::string &configname,
SyncConfig::ServerList &res);
SyncConfig::ConfigList &res);
/**
* set tree and nodes to VolatileConfigTree/Node
@ -1515,6 +1588,34 @@ class PersistentSyncSourceConfig : public SyncSourceConfig {
virtual const char* getSupportedTypes() const { return ""; }
};
/**
* Representing a configuration template node used for fuzzy matching.
*/
class TemplateConfig
{
boost::shared_ptr<FileConfigNode> m_metaNode;
ConfigProps m_metaProps;
string m_name;
public:
TemplateConfig (const string &path);
enum {
NO_MATCH = 0,
LEVEL1_MATCH = 1,
LEVEL2_MATCH = 2,
LEVEL3_MATCH = 3,
LEVEL4_MATCH = 4,
BEST_MATCH=5
};
static bool isTemplateConfig (const string &path);
virtual int metaMatch (const string &fingerprint, SyncConfig::MatchMode mode);
virtual int serverModeMatch (SyncConfig::MatchMode mode);
virtual int fingerprintMatch (const string &fingerprint);
virtual string getName();
virtual string getDescription();
virtual string getFingerprint();
};
/**@}*/

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = SyncEvolutionClient, SyncEvolution
description = SyncEvolution server side template

View File

@ -0,0 +1,4 @@
username = test
password = test
PeerIsClient = 1
ConsumerReady = 1

View File

@ -0,0 +1,2 @@
sync = two-way
uri = addressbook

View File

@ -0,0 +1,2 @@
sync = two-way
uri = calendar

View File

@ -0,0 +1,2 @@
sync = two-way
uri = memo

View File

@ -0,0 +1,2 @@
sync = two-way
uri = todo

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = ServerDefault
description = server side default template

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = Nokia_7210c
description = Nokia 7210c phone

View File

@ -0,0 +1,2 @@
PeerIsClient = 1
ConsumerReady = 1

View File

@ -0,0 +1,2 @@
sync = two-way
uri = Contacts

View File

@ -0,0 +1,4 @@
sync = two-way
type = virtual:text/x-calendar:1.0
evolutionsource = calendar,todo
uri = Calendar

View File

@ -0,0 +1 @@
sync = none

View File

@ -0,0 +1,2 @@
sync = two-way
uri = memo

View File

@ -0,0 +1 @@
sync = none

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = Nokia
description = Default templates for nokia phone

View File

@ -0,0 +1,3 @@
PeerIsClient = 0
fingerprint = Funambol
description = http://my.funambol.com

View File

@ -0,0 +1,6 @@
# *not* a complete config, do not copy manually into ~/.config
syncURL = http://my.funambol.com/sync
WebURL = http://my.funambol.com
enableWBXML = FALSE
ConsumerReady = TRUE

View File

@ -0,0 +1,2 @@
type = addressbook
uri = card

View File

@ -0,0 +1,3 @@
uri = event
sync = two-way
type = calendar:text/calendar!

View File

@ -0,0 +1 @@
uri = note

View File

@ -0,0 +1,3 @@
uri = task
sync = two-way
type = todo:text/calendar!

View File

@ -0,0 +1,3 @@
PeerIsClient = 0
fingerprint = ScheduleWorld, default
description = http://www.scheduleworld.org

View File

@ -0,0 +1,5 @@
# *not* a complete config, do not copy manually into ~/.config
syncURL = http://sync.scheduleworld.com/funambol/ds
WebURL = http://sync.scheduleworld.com
ConsumerReady = TRUE

View File

@ -0,0 +1,2 @@
type = addressbook:text/vcard
uri = card3

View File

@ -0,0 +1 @@
uri = cal2

View File

@ -0,0 +1 @@
uri = note

View File

@ -0,0 +1 @@
uri = task2

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = SyncEvolutionClient, SyncEvolution
description = SyncEvolution server side template

View File

@ -0,0 +1,4 @@
username = test
password = test
PeerIsClient = 1
ConsumerReady = 1

View File

@ -0,0 +1,2 @@
sync = two-way
uri = addressbook

View File

@ -0,0 +1,2 @@
sync = two-way
uri = calendar

View File

@ -0,0 +1,2 @@
sync = two-way
uri = memo

View File

@ -0,0 +1,2 @@
sync = two-way
uri = todo

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = ServerDefault
description = server side default template

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = Nokia_7210c
description = Nokia 7210c phone

View File

@ -0,0 +1,2 @@
PeerIsClient = 1
ConsumerReady = 1

View File

@ -0,0 +1,2 @@
sync = two-way
uri = Contacts

View File

@ -0,0 +1,2 @@
sync = two-way
uri = memo

View File

@ -0,0 +1,4 @@
sync = two-way
type = virtual:text/x-calendar:1.0
evolutionsource = calendar,todo
uri = Calendar

View File

@ -0,0 +1 @@
sync = none

View File

@ -0,0 +1,3 @@
peerIsClient = 1
fingerprint = Nokia
description = Default templates for nokia phone

View File

@ -0,0 +1,3 @@
peerIsClient = 0
fingerprint = Funambol
description = http://my.funambol.com

View File

@ -0,0 +1,6 @@
# *not* a complete config, do not copy manually into ~/.config
syncURL = http://my.funambol.com/sync
WebURL = http://my.funambol.com
enableWBXML = FALSE
ConsumerReady = TRUE

View File

@ -0,0 +1,2 @@
type = addressbook
uri = card

View File

@ -0,0 +1,3 @@
uri = event
sync = two-way
type = calendar:text/calendar!

View File

@ -0,0 +1 @@
uri = note

View File

@ -0,0 +1,3 @@
uri = task
sync = two-way
type = todo:text/calendar!

View File

@ -0,0 +1,3 @@
peerIsClient = 0
fingerprint = ScheduleWorld, default
description = http://sync.scheduleworld.com

View File

@ -0,0 +1,5 @@
# *not* a complete config, do not copy manually into ~/.config
syncURL = http://sync.scheduleworld.com/funambol/ds
WebURL = http://sync.scheduleworld.com
ConsumerReady = TRUE

View File

@ -0,0 +1,2 @@
type = addressbook:text/vcard
uri = card3

View File

@ -0,0 +1 @@
uri = cal2

View File

@ -0,0 +1 @@
uri = note

View File

@ -0,0 +1 @@
uri = task2