syncevo-dbus-server: removing/clearing of properties in shared configs (MB# 8059)

This patch introduces additional semantic for SetConfig(update=False, {})
(= removing configuration) and SetConfig(update=False, {<some sources removed>}
(= removing sources): when the config view is for a specific peer, then
only properties specific to that peer are removed.

When the view is peer-independent, it removes the complete
configuration (including all peers) resp. removes the shared and
per-peer source settings in all peers.

Global properties are never removed. With this patch, they can be read
by selecting the "@default" (= "") config view. Previously, reading
this view was rejected unless it contained real config nodes.

ConfigTree::removeSubtree() was merged into ConfigTree::remove(). The
functions were clearly related. The tree remains usable in the revised
remove() call.

SyncConfig::remove() did not work since the introduction of the "nodes
not in use" check in FileConfigTree::reset(). SyncConfig has to drop
all references to config nodes before FileConfigTree::remove() can do
its job. This is done via a new SyncConfig::makeVolatile().

test-dbus.py was updated to cover the new remove/clear
semantic. Because clearing sources with the "dummy-test-for-config"
view only removes some source properties, the remaining ones are still
reported in testClearConfigSources, breaking it. The test is
superseeded by the new tests and thus was removed.
This commit is contained in:
Patrick Ohly 2009-11-20 13:23:15 +01:00
parent cc107655f6
commit 7bd113e946
8 changed files with 128 additions and 68 deletions

View File

@ -25,8 +25,19 @@
directory if it is empty) which are not referenced by a
key in the configuration are removed. Setting a completely
empty configuration with "update=FALSE" can thus be used
to remove the entire
configuration.
to remove the entire configuration.
When a specific peer was selected via the configuration
name, clearing and removing properties is done only
for the peer-specific properties.
When no specific peer was selected, setting an empty
configuration with "update=FALSE" removes all source
settings and all peers. This allows starting from scratch;
setting a non-empty configuration with "update=FALSE"
will replace the peer-independent source properties with
those that are sent in the new configuration and remove
the sources which are not listed, also in all peers.
</doc:summary></doc:doc>
</arg>
<arg type="b" name="temporary" direction="in">

View File

@ -1160,7 +1160,9 @@ void ReadOperations::getConfig(bool getTemplate,
} else { ///< get a matching server configuration
boost::shared_ptr<SyncConfig> from;
syncConfig.reset(new SyncConfig(m_configName));
if (!syncConfig->exists()) {
// the default configuration can always be opened for reading,
// everything else must exist
if (!m_configName.empty() && !syncConfig->exists()) {
SE_THROW_EXCEPTION(NoSuchConfig, "No configuration '" + m_configName + "' found");
}
}

View File

@ -70,9 +70,14 @@ class ConfigTree {
/** ensure that all changes are saved persistently */
virtual void flush() = 0;
/** remove all configuration nodes and (if based on files)
directories created for them, if empty after file removal */
virtual void remove() = 0;
/**
* Remove all configuration nodes below and including a certain
* path and (if based on files) directories created for them, if
* empty after file removal.
*
* The nodes must not be in use for this to work.
*/
virtual void remove(const string &path) = 0;
/** a string identifying the root of the configuration - exact meaning varies */
virtual string getRootPath() const = 0;
@ -108,12 +113,6 @@ class ConfigTree {
* returns names of all existing nodes beneath the given path
*/
virtual list<string> getChildren(const string &path) = 0;
/**
* remove config nodes beneath the given path. If no other
* files, the directory will be removed too.
*/
virtual void removeSubtree(const string &path) = 0;
};

View File

@ -84,10 +84,11 @@ static bool rm_filter(const string &path, bool isDir)
}
}
void FileConfigTree::remove()
void FileConfigTree::remove(const string &path)
{
reset();
rm_r(m_root, rm_filter);
string fullpath = m_root + "/" + path;
clearNodes(fullpath);
rm_r(fullpath, rm_filter);
}
void FileConfigTree::reset()
@ -106,13 +107,6 @@ void FileConfigTree::reset()
m_nodes.clear();
}
void FileConfigTree::removeSubtree(const string &name)
{
string fullpath = m_root + "/" + name;
clearNodes(fullpath);
rm_r(fullpath, rm_filter);
}
void FileConfigTree::clearNodes(const string &fullpath)
{
NodeCache_t::iterator it;

View File

@ -53,13 +53,12 @@ class FileConfigTree : public ConfigTree {
/* ConfigTree API */
virtual string getRootPath() const;
virtual void flush();
virtual void remove();
virtual void remove(const string &path);
virtual void reset();
virtual boost::shared_ptr<ConfigNode> open(const string &path,
PropertyType type,
const string &otherId = string(""));
list<string> getChildren(const string &path);
virtual void removeSubtree(const string &path);
private:
/**

View File

@ -88,6 +88,11 @@ SyncConfig::SyncConfig() :
m_peerPath =
m_contextPath = "volatile";
makeVolatile();
}
void SyncConfig::makeVolatile()
{
m_tree.reset(new VolatileConfigTree());
m_peerNode.reset(new VolatileConfigNode());
m_hiddenPeerNode = m_peerNode;
@ -565,8 +570,14 @@ void SyncConfig::flush()
void SyncConfig::remove()
{
m_tree->remove();
m_tree.reset(new VolatileConfigTree());
boost::shared_ptr<ConfigTree> tree = m_tree;
// stop using the config nodes, they might get removed now
makeVolatile();
tree->remove(m_peerPath.empty() ?
m_contextPath :
m_peerPath);
}
boost::shared_ptr<PersistentSyncSourceConfig> SyncConfig::getSyncSourceConfig(const string &name)
@ -1227,20 +1238,25 @@ void SyncConfig::removeSyncSource(const string &name)
string pathName;
if (m_layout == SHARED_LAYOUT) {
// removed share source properties...
pathName = m_contextPath + "/sources/" + lower;
m_tree->removeSubtree(pathName);
// ... and the peer-specific ones of *all* peers
BOOST_FOREACH(const std::string peer,
m_tree->getChildren("peers")) {
m_tree->removeSubtree(string("peers/") + peer + "/sources/" + lower);
if (m_peerPath.empty()) {
// removed shared source properties...
pathName = m_contextPath + "/sources/" + lower;
m_tree->remove(pathName);
// ... and the peer-specific ones of *all* peers
BOOST_FOREACH(const std::string peer,
m_tree->getChildren("peers")) {
m_tree->remove(string("peers/") + peer + "/sources/" + lower);
}
} else {
// remove only inside the selected peer
m_tree->remove(m_peerPath + "/sources/" + lower);
}
} else {
// remove the peer-specific ones
pathName = m_peerPath +
(m_layout == SYNC4J_LAYOUT ? "spds/sources/" : "sources/") +
lower;
m_tree->removeSubtree(pathName);
m_tree->remove(pathName);
}
}

View File

@ -872,14 +872,16 @@ class SyncConfig {
void flush();
/**
* Remove the configuration. The config object itself is still
* valid afterwards, but empty and cannot be flushed.
* Remove the configuration. Config directories are removed if
* empty.
*
* When the configuration is peer-specific, only the peer's
* properties and config nodes are removed. Otherwise the complete
* configuration is removed, including all peers.
*
* Does *not* remove logs associated with the configuration.
* For that use the logdir handling in SyncContext
* before removing the configuration.
*
* The config directory is removed if it is empty.
*/
void remove();
@ -982,18 +984,26 @@ class SyncConfig {
void setSourceDefaults(const string &name, bool force = true);
/**
* remove sync source configuration. And remove the directory
* if it has no other files
* Remove sync source configuration. And remove the directory
* if it has no other files.
*
* When the configuration is peer-specific, only the peer's
* properties are removed. Otherwise the complete source
* configuration is removed, including properties stored
* for in any of the peers.
*/
void removeSyncSource(const string &name);
/**
* clear existing visible properties in config.ini
* clear existing visible source properties selected by the
* configuration: with or without peer-specific properties,
* depending on the current view
*/
void clearSyncSourceProperties(const string &name);
/**
* clear all global sync properties
* clear all global sync properties, with or without
* peer-specific properties, depending on the current view
*/
void clearSyncProperties();
@ -1205,6 +1215,11 @@ private:
const std::string &configname,
SyncConfig::ServerList &res);
/**
* set tree and nodes to VolatileConfigTree/Node
*/
void makeVolatile();
/**
* String that identifies the peer, see constructor.
* This is a normalized string (normalizePeerString()).

View File

@ -786,21 +786,7 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
config = self.session.GetConfig(False, utf8_strings=True)
except dbus.DBusException, ex:
self.failUnlessEqual(str(ex),
"org.syncevolution.NoSuchConfig: No configuration 'dummy-test' found")
def testClearConfigSources(self):
""" test sources related configs are cleared correctly. """
self.setupConfig()
config1 = {
"" : { "syncURL" : "http://my.funambol.com/sync",
"username" : "unknown",
"password" : "secret",
"deviceId" : "foo"
}
}
self.session.SetConfig(False, False, config1, utf8_strings=True)
config2 = self.session.GetConfig(False, utf8_strings=True)
self.failUnlessEqual(config2, config1)
"org.syncevolution.NoSuchConfig: No configuration 'dummy-test' found")
def testCheckSourceNoConfig(self):
""" test the right error is reported when the server doesn't exist """
@ -1287,7 +1273,9 @@ class TestMultipleConfigs(unittest.TestCase, DBusUtil):
self.session.SetConfig(True, False,
{ "" : { "defaultPeer" : "foobar_peer",
"syncURL": "http://scheduleworld" },
"source/calendar" : { "uri" : "cal3" },
"source/addressbook" : { "evolutionsource": "Personal",
"sync" : "two-way",
"uri": "card3" } },
utf8_strings=True)
self.session.Detach()
@ -1299,7 +1287,9 @@ class TestMultipleConfigs(unittest.TestCase, DBusUtil):
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Personal")
self.session.SetConfig(True, False,
{ "" : { "syncURL": "http://funambol" },
"source/calendar" : { "uri" : "cal" },
"source/addressbook" : { "evolutionsource": "Work",
"sync" : "refresh-from-client",
"uri": "card" } },
utf8_strings=True)
self.session.Detach()
@ -1360,6 +1350,36 @@ class TestMultipleConfigs(unittest.TestCase, DBusUtil):
self.failUnlessEqual(config["source/addressbook"]["uri"], "card3")
self.session.Detach()
# remove "addressbook" source in "foo"
self.setUpSession("foo")
config = self.session.GetConfig(False, utf8_strings=True)
del config["source/addressbook"]
self.session.SetConfig(False, False, config, utf8_strings=True)
self.session.Detach()
# "addressbook" still exists in "foo" but only with default values
config = self.server.GetConfig("foo", False, utf8_strings=True)
self.failIf("uri" in config["source/addressbook"])
self.failIf("sync" in config["source/addressbook"])
# "addressbook" unchanged in "bar"
config = self.server.GetConfig("bar", False, utf8_strings=True)
self.failUnlessEqual(config["source/addressbook"]["uri"], "card")
self.failUnlessEqual(config["source/addressbook"]["sync"], "refresh-from-client")
# remove "addressbook" everywhere
self.setUpSession("")
config = self.session.GetConfig(False, utf8_strings=True)
del config["source/addressbook"]
self.session.SetConfig(False, False, config, utf8_strings=True)
self.session.Detach()
# "addressbook" gone in "foo" and "bar"
config = self.server.GetConfig("foo", False, utf8_strings=True)
self.failIf("source/addressbook" in config)
config = self.server.GetConfig("bar", False, utf8_strings=True)
self.failIf("source/addressbook" in config)
# check listing of peers while removing "bar"
self.setUpSession("bar")
peers = self.session.GetConfigs(False, utf8_strings=True)
@ -1367,28 +1387,32 @@ class TestMultipleConfigs(unittest.TestCase, DBusUtil):
[ "bar", "foo", "foo@other_context" ])
peers2 = self.server.GetConfigs(False, utf8_strings=True)
self.failUnlessEqual(peers, peers2)
# remove "foo"
# remove "bar"
self.session.SetConfig(False, False, {}, utf8_strings=True)
# The other peers should not have been affected, but currently
# they are (MB #8059). Decide about this and remove the return.
return
peers = self.server.GetConfigs(False, utf8_strings=True)
self.failUnlessEqual(peers,
[ "foo", "foo@other_context" ])
self.session.Detach()
config = self.GetConfig("foo", False, utf8_strings=True)
# other configs should not have been affected
config = self.server.GetConfig("foo", False, utf8_strings=True)
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld")
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Work")
self.failUnlessEqual(config["source/addressbook"]["uri"], "card3")
config = self.GetConfig("foo@other_context", False, utf8_strings=True)
self.failUnlessEqual(config["source/calendar"]["uri"], "cal3")
config = self.server.GetConfig("foo@other_context", False, utf8_strings=True)
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
self.failUnlessEqual(config[""]["syncURL"], "http://scheduleworld2")
self.failUnlessEqual(config["source/addressbook"]["evolutionsource"], "Play")
self.failUnlessEqual(config["source/addressbook"]["uri"], "card30")
# remove complete config
self.setUpSession("")
self.session.SetConfig(False, False, {}, utf8_strings=True)
config = self.session.GetConfig(False, utf8_strings=True)
self.failUnlessEqual(config[""]["defaultPeer"], "foobar_peer")
peers = self.server.GetConfigs(False, utf8_strings=True)
self.failUnlessEqual(peers, ['foo@other_context'])
self.session.Detach()
if __name__ == '__main__':
unittest.main()