Skip to content

v0.2.47..v0.2.48 changeset OsmApiChangeset.cpp

Garret Voltz edited this page Sep 27, 2019 · 1 revision
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;
+}
 
 }
Clone this wiki locally