diff --git a/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp b/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
index e8af8ac..6a8f95e 100644
--- a/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
+++ b/hoot-core/src/main/cpp/hoot/core/io/OsmApiChangeset.cpp
@@ -68,64 +68,82 @@ XmlChangeset::XmlChangeset(const QList<QString> &changesets)
loadChangeset(*it);
}
-void XmlChangeset::loadChangeset(const QString &changesetPath)
+void XmlChangeset::loadChangeset(const QString &changeset)
{
- /* <osmChange version="0.6" generator="acme osm editor">
- * <create|modify|delete>
- * <node|way|relation.../>
- * ...
- * </create|modify|delete>
- * ...
- * </osmChange>
- */
- QFile file(changesetPath);
+ if (QFile::exists(changeset))
+ loadChangesetFile(changeset);
+ else
+ loadChangesetXml(changeset);
+}
+
+void XmlChangeset::loadChangesetFile(const QString& path)
+{
+ // Attempt to open the file
+ QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
- LOG_ERROR("Unable to read file at: " << changesetPath);
+ LOG_ERROR("Unable to read file at: " << path);
return;
}
// Open the XML stream reader and attach it to the file
QXmlStreamReader reader(&file);
+ // Read the OSC changeset
+ if (path.endsWith(".osc"))
+ loadChangeset(reader);
+ else // .osm file
+ loadOsmAsChangeset(reader);
+}
+
+void XmlChangeset::loadChangesetXml(const QString &changesetXml)
+{
+ // Load the XML directly into the reader
+ QXmlStreamReader reader(changesetXml);
+ // Load the changeset formatted XML
+ loadChangeset(reader);
+}
+
+void XmlChangeset::loadChangeset(QXmlStreamReader &reader)
+{
// Make sure that the XML provided starts with the <diffResult> tag
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartDocument)
type = reader.readNext();
- // Read the OSC changeset
- if (changesetPath.endsWith(".osc"))
+ if (type == QXmlStreamReader::StartElement && reader.name() != "osmChange")
{
- if (type == QXmlStreamReader::StartElement && reader.name() != "osmChange")
- {
- LOG_ERROR("Unknown changeset file format.");
- return;
- }
- // Iterate all of updates and record them
- while (!reader.atEnd() && !reader.hasError())
- {
- type = reader.readNext();
- if (type == QXmlStreamReader::StartElement)
- {
- QStringRef name = reader.name();
- if (name == "create")
- loadElements(reader, ChangesetType::TypeCreate);
- else if (name == "modify")
- loadElements(reader, ChangesetType::TypeModify);
- else if (name == "delete")
- loadElements(reader, ChangesetType::TypeDelete);
- }
- }
+ LOG_ERROR("Unknown changeset file format.");
+ return;
}
- else // .osm file
+ // Iterate all of updates and record them
+ while (!reader.atEnd() && !reader.hasError())
{
- if (type == QXmlStreamReader::StartElement && reader.name() != "osm")
+ type = reader.readNext();
+ if (type == QXmlStreamReader::StartElement)
{
- LOG_ERROR("Unknown OSM XML file format.");
- return;
+ QStringRef name = reader.name();
+ if (name == "create")
+ loadElements(reader, ChangesetType::TypeCreate);
+ else if (name == "modify")
+ loadElements(reader, ChangesetType::TypeModify);
+ else if (name == "delete")
+ loadElements(reader, ChangesetType::TypeDelete);
}
- // Force load all of the elements as 'create' elements
- loadElements(reader, ChangesetType::TypeCreate);
}
}
+void XmlChangeset::loadOsmAsChangeset(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType type = reader.readNext();
+ if (type == QXmlStreamReader::StartDocument)
+ type = reader.readNext();
+ if (type == QXmlStreamReader::StartElement && reader.name() != "osm")
+ {
+ LOG_ERROR("Unknown OSM XML file format.");
+ return;
+ }
+ // Force load all of the elements as 'create' elements
+ loadElements(reader, ChangesetType::TypeCreate);
+}
+
void XmlChangeset::loadElements(QXmlStreamReader& reader, ChangesetType changeset_type)
{
XmlElementPtr element;
@@ -465,6 +483,23 @@ bool XmlChangeset::addNode(ChangesetInfoPtr& changeset, ChangesetType type, XmlN
return false;
}
+void XmlChangeset::moveOrRemoveNode(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlNode* node)
+{
+ // Move the node from source to destination if possible, or remove it from the source
+ if (canMoveNode(source, destination, type, node))
+ {
+ // Move the node
+ moveNode(source, destination, type, node);
+ }
+ else
+ {
+ // Remove only the node
+ source->remove(ElementType::Node, type, node->id());
+ // Set the node to available
+ node->setStatus(XmlElement::ElementStatus::Available);
+ }
+}
+
bool XmlChangeset::moveNode(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlNode* node)
{
// Add the node to the destination and remove from the source
@@ -573,6 +608,22 @@ bool XmlChangeset::addParentWays(ChangesetInfoPtr& changeset, const std::set<lon
return sendable;
}
+void XmlChangeset::moveOrRemoveWay(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlWay* way)
+{
+ if (canMoveWay(source, destination, type, way))
+ {
+ // Move the way and anything associated
+ moveWay(source, destination, type, way);
+ }
+ else
+ {
+ // Remove only the way from the changeset, not its nodes
+ source->remove(ElementType::Way, type, way->id());
+ // Set the way to available
+ way->setStatus(XmlElement::ElementStatus::Available);
+ }
+}
+
bool XmlChangeset::moveWay(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlWay* way)
{
// Don't worry about the contents of a delete operation
@@ -659,8 +710,10 @@ bool XmlChangeset::addRelation(ChangesetInfoPtr& changeset, ChangesetType type,
// Make sure that the ID is negative (create) in the ID map
if (_idMap.getNewId(ElementType::Relation, member.getRef()) < 0)
{
- XmlRelation* relation = dynamic_cast<XmlRelation*>(_allRelations[member.getRef()].get());
- addRelation(changeset, type, relation);
+ XmlRelation* relation_member = dynamic_cast<XmlRelation*>(_allRelations[member.getRef()].get());
+ // Don't re-add self referencing relations
+ if (relation->id() != relation_member->id())
+ addRelation(changeset, type, relation_member);
}
}
}
@@ -729,6 +782,22 @@ bool XmlChangeset::addParentRelations(ChangesetInfoPtr& changeset, const std::se
}
+void XmlChangeset::moveOrRemoveRelation(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlRelation* relation)
+{
+ if (canMoveRelation(source, destination, type, relation))
+ {
+ // Move the relation and anything associated
+ moveRelation(source, destination, type, relation);
+ }
+ else
+ {
+ // Remove only the relation from the changeset, not its members
+ source->remove(ElementType::Relation, type, relation->id());
+ // Set the relation to available
+ relation->setStatus(XmlElement::ElementStatus::Available);
+ }
+}
+
bool XmlChangeset::moveRelation(ChangesetInfoPtr& source, ChangesetInfoPtr& destination, ChangesetType type, XmlRelation* relation)
{
// Don't worry about the contents of a delete operation
@@ -759,10 +828,14 @@ bool XmlChangeset::moveRelation(ChangesetInfoPtr& source, ChangesetInfoPtr& dest
}
else if (member.isRelation())
{
- for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
+ // Don't attempt to move circular relation references
+ if (id != relation->id())
{
- if (source->contains(ElementType::Relation, (ChangesetType)current_type, id))
- moveRelation(source, destination, (ChangesetType)current_type, dynamic_cast<XmlRelation*>(_allRelations[id].get()));
+ for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
+ {
+ if (source->contains(ElementType::Relation, (ChangesetType)current_type, id))
+ moveRelation(source, destination, (ChangesetType)current_type, dynamic_cast<XmlRelation*>(_allRelations[id].get()));
+ }
}
}
}
@@ -839,10 +912,14 @@ size_t XmlChangeset::getObjectCount(ChangesetInfoPtr& changeset, XmlRelation* re
}
else if (member.isRelation())
{
- for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
+ // Don't recount circular referenced relations
+ if (id != relation->id())
{
- if (changeset->contains(ElementType::Relation, (ChangesetType)current_type, id))
- count += getObjectCount(changeset, dynamic_cast<XmlRelation*>(_allRelations[id].get()));
+ for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
+ {
+ if (changeset->contains(ElementType::Relation, (ChangesetType)current_type, id))
+ count += getObjectCount(changeset, dynamic_cast<XmlRelation*>(_allRelations[id].get()));
+ }
}
}
}
@@ -940,6 +1017,9 @@ bool XmlChangeset::canSend(XmlRelation* relation)
// Special case, relation doesn't exist in changeset, it may in database, send it
if (member.getRef() > 0 && _allRelations.find(member.getRef()) == _allRelations.end())
return true;
+ // Special case, relation has a member relation that is itself, send it
+ else if (member.getRef() == relation->id())
+ return true;
// Check if the relation exists and can't be sent
else if (_allRelations.find(member.getRef()) != _allWays.end() &&
!isSent(_allRelations[member.getRef()].get()) &&
@@ -1059,6 +1139,7 @@ bool XmlChangeset::matchesChangesetPreconditionFailure(const QString& hint,
long& member_id, ElementType::Type& member_type,
long& element_id, ElementType::Type& element_type)
{
+ // Precondition failed: Node 55 is still used by ways 123
QRegularExpression reg(
"Precondition failed: (Node|Way|Relation) (-?[0-9]+) is still used by (node|way|relation)s (-?[0-9]+)",
QRegularExpression::CaseInsensitiveOption);
@@ -1077,6 +1158,30 @@ bool XmlChangeset::matchesChangesetPreconditionFailure(const QString& hint,
return false;
}
+bool XmlChangeset::matchesChangesetConflictVersionMismatchFailure(const QString& hint,
+ long& element_id, ElementType::Type& element_type,
+ long& version_old, long& version_new)
+{
+ // Changeset conflict: Version mismatch: Provided 2, server had: 1 of Node 4869875616
+ QRegularExpression reg(
+ "Changeset conflict: Version mismatch: Provided ([0-9]+), server had: ([0-9]+) of (Node|Way|Relation) ([0-9]+)",
+ QRegularExpression::CaseInsensitiveOption);
+ QRegularExpressionMatch match = reg.match(hint);
+ if (match.hasMatch())
+ {
+ bool success = false;
+ version_old = match.captured(1).toLong(&success);
+ if (!success)
+ return success;
+ version_new = match.captured(2).toLong(&success);
+ if (!success)
+ return success;
+ element_type = ElementType::fromString(match.captured(3).toLower());
+ element_id = match.captured(4).toLong(&success);
+ return success;
+ }
+ return false;
+}
ChangesetInfoPtr XmlChangeset::splitChangeset(ChangesetInfoPtr changeset, const QString& splitHint)
{
@@ -1210,19 +1315,8 @@ ChangesetInfoPtr XmlChangeset::splitChangeset(ChangesetInfoPtr changeset, const
{
long id = changeset->getFirst(ElementType::Relation, (ChangesetType)current_type);
XmlRelation* relation = dynamic_cast<XmlRelation*>(_allRelations[id].get());
-
- if (canMoveRelation(changeset, split, (ChangesetType)current_type, relation))
- {
- // Move the relation and anything associated
- moveRelation(changeset, split, (ChangesetType)current_type, relation);
- }
- else
- {
- // Remove only the relation from the changeset, not its members
- changeset->remove(ElementType::Relation, (ChangesetType)current_type, relation->id());
- // Set the relation to available
- relation->setStatus(XmlElement::ElementStatus::Available);
- }
+ // Move the relation to the new changeset if possible or remove it and make it available again
+ moveOrRemoveRelation(changeset, split, (ChangesetType)current_type, relation);
// If the split is big enough, end the operation
if (split->size() >= splitSize)
return split;
@@ -1236,18 +1330,8 @@ ChangesetInfoPtr XmlChangeset::splitChangeset(ChangesetInfoPtr changeset, const
{
long id = changeset->getFirst(ElementType::Way, (ChangesetType)current_type);
XmlWay* way = dynamic_cast<XmlWay*>(_allWays[id].get());
- if (canMoveWay(changeset, split, (ChangesetType)current_type, way))
- {
- // Move the way and anything associated
- moveWay(changeset, split, (ChangesetType)current_type, way);
- }
- else
- {
- // Remove only the way from the changeset, not its nodes
- changeset->remove(ElementType::Way, (ChangesetType)current_type, way->id());
- // Set the way to available
- way->setStatus(XmlElement::ElementStatus::Available);
- }
+ // Move the way to the new changeset if possible or remove it and make it available again
+ moveOrRemoveWay(changeset, split, (ChangesetType)current_type, way);
// If the split is big enough, end the operation
if (split->size() >= splitSize)
return split;
@@ -1261,18 +1345,8 @@ ChangesetInfoPtr XmlChangeset::splitChangeset(ChangesetInfoPtr changeset, const
{
long id = changeset->getFirst(ElementType::Node, (ChangesetType)current_type);
XmlNode* node = dynamic_cast<XmlNode*>(_allNodes[id].get());
- if (canMoveNode(changeset, split, (ChangesetType)current_type, node))
- {
- // Move the node
- moveNode(changeset, split, (ChangesetType)current_type, node);
- }
- else
- {
- // Remove only the node
- changeset->remove(ElementType::Node, (ChangesetType)current_type, node->id());
- // Set the node to available
- node->setStatus(XmlElement::ElementStatus::Available);
- }
+ // Move the node to the new changeset if possible or remove it and make it available again
+ moveOrRemoveNode(changeset, split, (ChangesetType)current_type, node);
// If the split is big enough, end the operation
if (split->size() >= splitSize)
return split;
@@ -1282,13 +1356,13 @@ ChangesetInfoPtr XmlChangeset::splitChangeset(ChangesetInfoPtr changeset, const
return split;
}
-void XmlChangeset::updateFailedChangeset(ChangesetInfoPtr changeset)
+void XmlChangeset::updateFailedChangeset(ChangesetInfoPtr changeset, bool forceFailure)
{
// Only set the failed status on single element changesets
- if (changeset->size() != 1)
+ if (changeset->size() != 1 && !forceFailure)
return;
// Don't set the elements to failed yet, until after they are tried
- if (!changeset->getChangesetIssuesResolved())
+ if (!changeset->getAttemptedResolveChangesetIssues())
return;
// Iterate the three changeset type arrays looking for elements to mark
for (int current_type = ChangesetType::TypeCreate; current_type != ChangesetType::TypeMax; ++current_type)
@@ -1465,11 +1539,13 @@ bool XmlChangeset::fixElement(ChangesetTypeMap& map, long id, long version, QMap
tags.remove(key);
}
// Add in any tags that are missing
- QXmlStreamAttributes attributes;
for (QMap<QString, QString>::iterator it = tags.begin(); it != tags.end(); ++it)
- attributes.append("", it.key(), it.value());
- if (attributes.size() > 0)
- element->addTag(XmlObject("tag", QXmlStreamAttributes()));
+ {
+ QXmlStreamAttributes attributes;
+ attributes.append("", "k", it.key());
+ attributes.append("", "v", it.value());
+ element->addTag(XmlObject("tag", attributes));
+ }
}
}
return success;
@@ -1587,5 +1663,97 @@ void XmlChangeset::getRelations(const ChangesetInfoPtr& changeset, QTextStream&
}
}
+ChangesetInfo::ChangesetInfo()
+ : _attemptedResolveChangesetIssues(false),
+ _numRetries(0)
+{
+}
+
+void ChangesetInfo::add(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type, long id)
+{
+ _changeset[element_type][changeset_type].insert(id);
+ // Changes in the changeset cause the retries to start over
+ _numRetries = 0;
+}
+
+void ChangesetInfo::remove(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type, long id)
+{
+ container& selectedSet = _changeset[element_type][changeset_type];
+ if (selectedSet.find(id) != selectedSet.end())
+ selectedSet.erase(id);
+ // Changes in the changeset cause the retries to start over
+ _numRetries = 0;
+}
+
+long ChangesetInfo::getFirst(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type)
+{
+ return *(_changeset[element_type][changeset_type].begin());
+}
+
+void ChangesetInfo::clear()
+{
+ for (int i = 0; i < (int)ElementType::Unknown; ++i)
+ {
+ for (int j = 0; j < (int)XmlChangeset::TypeMax; ++j)
+ _changeset[i][j].clear();
+ }
+ _numRetries = 0;
+}
+
+bool ChangesetInfo::contains(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type, long id)
+{
+ return _changeset[element_type][changeset_type].find(id) != end(element_type, changeset_type);
+}
+
+ChangesetInfo::iterator ChangesetInfo::begin(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type)
+{
+ return _changeset[element_type][changeset_type].begin();
+}
+
+ChangesetInfo::iterator ChangesetInfo::end(ElementType::Type element_type, XmlChangeset::ChangesetType changeset_type)
+{
+ return _changeset[element_type][changeset_type].end();
+}
+
+size_t ChangesetInfo::size(ElementType::Type elementType, XmlChangeset::ChangesetType changesetType)
+{
+ return _changeset[(int)elementType][(int)changesetType].size();
+}
+
+size_t ChangesetInfo::size()
+{
+ size_t s = 0;
+ // Iterate element types
+ for (int i = 0; i < (int)ElementType::Unknown; ++i)
+ {
+ // Sum up all counts for each changeset type
+ for (int j = 0; j < (int)XmlChangeset::TypeMax; ++j)
+ s += _changeset[i][j].size();
+ }
+ return s;
+}
+
+bool ChangesetInfo::getAttemptedResolveChangesetIssues()
+{
+ return _attemptedResolveChangesetIssues;
+}
+
+void ChangesetInfo::setAttemptedResolveChangesetIssues(bool attempted)
+{
+ _attemptedResolveChangesetIssues = attempted;
+}
+
+bool ChangesetInfo::canRetry()
+{
+ return _numRetries < MAX_RETRIES;
+}
+
+void ChangesetInfo::retry()
+{
+ // Increment the retry count
+ _numRetries++;
+ // Once the retry count reaches MAX_RETRIES set the attempted resolved flag
+ _attemptedResolveChangesetIssues = true;
+}
}