syncevolution/src/syncevo/MapSyncSource.cpp

364 lines
13 KiB
C++

/*
* Copyright (C) 2010 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <syncevo/MapSyncSource.h>
#include <syncevo/PrefixConfigNode.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
SDKInterface *SubSyncSource::getSynthesisAPI() const
{
return m_parent ?
m_parent->getSynthesisAPI() :
nullptr;
}
MapSyncSource::MapSyncSource(const SyncSourceParams &params,
const std::shared_ptr<SubSyncSource> &sub) :
TestingSyncSource(params),
m_sub(sub)
{
auto safeNode = std::make_shared<SafeConfigNode>(params.m_nodes.getTrackingNode());
m_trackingNode.reset(new PrefixConfigNode("item-",
std::static_pointer_cast<ConfigNode>(safeNode)));
m_metaNode = safeNode;
// parameters don't matter because actual implementation is in sub source
SyncSourceLogging::init(std::list<std::string>(),
", ",
m_operations);
m_sub->setParent(this);
// Redirect backup/restore into sub source, if it defines a backup
// operation. Otherwise continue to use our own,
// SyncSourceRevision based implementation. The expectation is
// that a custom backup operation implies a custom restore,
// because of custom data formats in the data dump. Therefore if()
// only checks the m_backupData pointer.
if (m_sub->getOperations().m_backupData) {
m_operations.m_backupData = m_sub->getOperations().m_backupData;
m_operations.m_restoreData = m_sub->getOperations().m_restoreData;
}
m_operations.m_isEmpty = m_sub->getOperations().m_isEmpty;
}
void MapSyncSource::enableServerMode()
{
SyncSourceAdmin::init(m_operations, this);
SyncSourceBlob::init(m_operations, getCacheDir());
}
bool MapSyncSource::serverModeEnabled() const
{
return m_operations.m_loadAdminData;
}
void MapSyncSource::detectChanges(SyncSourceRevisions::ChangeMode mode)
{
// erase content which might have been set in a previous call
reset();
// read old list from node (matches endSync() code)
m_revisions.clear();
ConfigProps props;
m_trackingNode->readProperties(props);
for (const auto &prop: props) {
const std::string &mainid = prop.first;
const std::string &value = prop.second;
size_t pos = value.find('/');
bool okay = false;
if (pos == 0) {
pos = value.find('/', 1);
if (pos != value.npos) {
std::string revision = m_escape.unescape(value.substr(1, pos - 1));
size_t nextpos = value.find('/', pos + 1);
if (nextpos != value.npos) {
std::string uid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1));
SubRevisionEntry &ids = m_revisions[mainid];
ids.m_revision = revision;
ids.m_uid = uid;
pos = nextpos;
while ((nextpos = value.find('/', pos + 1)) != value.npos) {
std::string subid = m_escape.unescape(value.substr(pos + 1, nextpos - pos - 1));
ids.m_subids.insert(subid);
pos = nextpos;
}
okay = true;
}
}
}
if (!okay) {
SE_LOG_DEBUG(getDisplayName(), "unsupported or corrupt revision entry: %s = %s",
mainid.c_str(),
value.c_str());
}
}
// determine how to update that list and find changes
switch (mode) {
case SyncSourceRevisions::CHANGES_NONE:
// nothing to do, just tell sub source
m_sub->setAllSubItems(m_revisions);
break;
case SyncSourceRevisions::CHANGES_FULL: {
// update the list and compare to find changes
SubRevisionMap_t newRevisions;
if (m_revisions.empty()) {
// nothing to reuse, just ask for current items
m_sub->listAllSubItems(newRevisions);
} else {
// update old information
newRevisions = m_revisions;
m_sub->updateAllSubItems(newRevisions);
}
// deleted items...
for (const auto &entry: m_revisions) {
const std::string &mainid = entry.first;
const SubRevisionEntry &ids = entry.second;
if (newRevisions.find(entry.first) == newRevisions.end()) {
for (const std::string &subid: ids.m_subids) {
addItem(createLUID(mainid, subid), DELETED);
}
}
}
// added or updated items...
for (const auto &entry: newRevisions) {
const std::string &mainid = entry.first;
const SubRevisionEntry &ids = entry.second;
auto it = m_revisions.find(entry.first);
if (it == m_revisions.end()) {
// all sub-items are added
for (const std::string &subid: ids.m_subids) {
addItem(createLUID(mainid, subid), NEW);
}
} else if (it->second.m_revision != ids.m_revision) {
// merged item was modified, some of its sub-items
// might have been removed...
for (const std::string &subid: it->second.m_subids) {
if (ids.m_subids.find(subid) == ids.m_subids.end()) {
addItem(createLUID(mainid, subid), DELETED);
}
}
// ... or added/modified
for (const std::string &subid: ids.m_subids) {
if (it->second.m_subids.find(subid) == it->second.m_subids.end()){
addItem(createLUID(mainid, subid), NEW);
} else {
addItem(createLUID(mainid, subid), UPDATED);
}
}
}
}
// continue with up-to-date list
m_revisions = newRevisions;
break;
}
case SyncSourceRevisions::CHANGES_SLOW:
// replace with current list, don't bother about finding changes
m_revisions.clear();
m_sub->listAllSubItems(m_revisions);
break;
default:
// ?!
throwError(SE_HERE, "unknown change mode");
break;
}
// always set the full list of luids in SyncSourceChanges
for (const auto &entry: m_revisions) {
const std::string &mainid = entry.first;
const SubRevisionEntry &ids = entry.second;
for (const std::string &subid: ids.m_subids) {
addItem(createLUID(mainid, subid));
}
}
}
void MapSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
{
m_sub->begin();
// TODO: copy-and-pasted from TrackingSyncSource::beginSync() - refactor!
// vvvvvvvvvvvvvvvvv
// use the most reliable (and most expensive) method by default
SyncSourceRevisions::ChangeMode mode = SyncSourceRevisions::CHANGES_FULL;
// resume token overrides the normal token; safe to ignore in most
// cases and this detectChanges() is done independently of the
// token, but let's do it right here anyway
string token;
if (!resumeToken.empty()) {
token = resumeToken;
} else {
token = lastToken;
}
// slow sync if token is empty
if (token.empty()) {
SE_LOG_DEBUG(getDisplayName(), "slow sync or testing, do full item scan to detect changes");
mode = SyncSourceRevisions::CHANGES_SLOW;
} else {
string oldRevision = m_metaNode->readProperty("databaseRevision");
if (!oldRevision.empty()) {
string newRevision = m_sub->subDatabaseRevision();
SE_LOG_DEBUG(getDisplayName(), "old database revision '%s', new revision '%s'",
oldRevision.c_str(),
newRevision.c_str());
if (newRevision == oldRevision) {
SE_LOG_DEBUG(getDisplayName(), "revisions match, no item changes");
mode = SyncSourceRevisions::CHANGES_NONE;
}
// Reset old revision. If anything goes wrong, then we
// don't want to rely on a possibly incorrect optimization.
m_metaNode->setProperty("databaseRevision", "");
m_metaNode->flush();
}
}
if (mode == SyncSourceRevisions::CHANGES_FULL) {
SE_LOG_DEBUG(getDisplayName(), "using full item scan to detect changes");
}
detectChanges(mode);
// TODO: refactor ^^^^^^^^^^^^^^^^^^^^^^^^
}
std::string MapSyncSource::endSync(bool success)
{
m_sub->endSubSync(success);
// TODO: refactor vvvvvvvvvvvvvvvv
if (success) {
string updatedRevision = m_sub->subDatabaseRevision();
m_metaNode->setProperty("databaseRevision", updatedRevision);
// This part is different from TrackingSyncSource: our luid/rev information
// is in m_revisions and only gets dumped into m_trackingNode at the very end here.
m_trackingNode->clear();
for (const auto &entry: m_revisions) {
const std::string &mainid = entry.first;
const SubRevisionEntry &ids = entry.second;
std::stringstream buffer;
buffer << '/' << m_escape.escape(ids.m_revision) << '/';
buffer << m_escape.escape(ids.m_uid) << '/';
for (const std::string &subid: ids.m_subids) {
buffer << m_escape.escape(subid) << '/';
}
m_trackingNode->setProperty(mainid, buffer.str());
}
// flush both nodes, just in case; in practice, the properties
// end up in the same file and only get flushed once
m_trackingNode->flush();
m_metaNode->flush();
} else {
// The Synthesis docs say that we should rollback in case of
// failure. Cannot do that for data, so lets at least keep
// the revision map unchanged.
}
// no token handling at the moment (not needed for clients):
// return a non-empty token to distinguish an incremental
// sync from a slow sync in beginSync()
return "1";
// TODO: refactor ^^^^^^^^^^^^^^^^^^^
}
SyncSourceRaw::InsertItemResult MapSyncSource::insertItem(const std::string &luid, const std::string &item)
{
StringPair ids = splitLUID(luid);
SubSyncSource::SubItemResult res = m_sub->insertSubItem(ids.first, ids.second, item);
// anything changed?
if (res.m_state != ITEM_NEEDS_MERGE) {
SubRevisionEntry &entry = m_revisions[res.m_mainid];
entry.m_uid = res.m_uid;
entry.m_revision = res.m_revision;
entry.m_subids.insert(res.m_subid);
}
return SyncSourceRaw::InsertItemResult(createLUID(res.m_mainid, res.m_subid),
res.m_revision, res.m_state);
}
void MapSyncSource::readItem(const std::string &luid, std::string &item)
{
StringPair ids = splitLUID(luid);
m_sub->readSubItem(ids.first, ids.second, item);
}
void MapSyncSource::deleteItem(const string &luid)
{
StringPair ids = splitLUID(luid);
std::string rev = m_sub->removeSubItem(ids.first, ids.second);
auto it = m_revisions.find(ids.first);
if (it != m_revisions.end()) {
it->second.m_subids.erase(ids.second);
if (it->second.m_subids.empty()) {
// last sub item removed, remove merged item
m_revisions.erase(it);
} else {
// still some sub items, update revision in merged item
it->second.m_revision = rev;
}
}
}
std::string MapSyncSource::getDescription(const string &luid)
{
StringPair ids = splitLUID(luid);
return m_sub->getSubDescription(ids.first, ids.second);
}
std::string MapSyncSource::createLUID(const std::string &uid, const std::string &subid)
{
std::string luid = m_escape.escape(uid);
if (!subid.empty()) {
luid += '/';
luid += m_escape.escape(subid);
}
return luid;
}
std::pair<std::string, std::string> MapSyncSource::splitLUID(const std::string &luid)
{
size_t index = luid.find('/');
if (index != luid.npos) {
return make_pair(m_escape.unescape(luid.substr(0, index)),
m_escape.unescape(luid.substr(index + 1)));
} else {
return make_pair(m_escape.unescape(luid), "");
}
}
StringEscape MapSyncSource::m_escape('%', "/");
void MapSyncSource::removeAllItems()
{
for (const auto &entry: m_revisions) {
m_sub->removeMergedItem(entry.first);
}
m_revisions.clear();
}
SE_END_CXX