mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
[editor] Fixes for the OSM uploading code
Signed-off-by: map-per <map-per@gmx.de>
This commit is contained in:
@@ -142,7 +142,7 @@ void Notes::CreateNote(ms::LatLon const & latLon, std::string const & text)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> g(m_mu);
|
std::lock_guard<std::mutex> g(m_dataAccessMutex);
|
||||||
auto const it = std::find_if(m_notes.begin(), m_notes.end(), [&latLon, &text](Note const & note)
|
auto const it = std::find_if(m_notes.begin(), m_notes.end(), [&latLon, &text](Note const & note)
|
||||||
{ return latLon.EqualDxDy(note.m_point, kTolerance) && text == note.m_note; });
|
{ return latLon.EqualDxDy(note.m_point, kTolerance) && text == note.m_note; });
|
||||||
// No need to add the same note. It works in case when saved note are not uploaded yet.
|
// No need to add the same note. It works in case when saved note are not uploaded yet.
|
||||||
@@ -155,61 +155,56 @@ void Notes::CreateNote(ms::LatLon const & latLon, std::string const & text)
|
|||||||
|
|
||||||
void Notes::Upload(osm::OsmOAuth const & auth)
|
void Notes::Upload(osm::OsmOAuth const & auth)
|
||||||
{
|
{
|
||||||
// Capture self to keep it from destruction until this thread is done.
|
std::unique_lock<std::mutex> uploadingNotesLock(m_uploadingNotesMutex, std::defer_lock);
|
||||||
auto const self = shared_from_this();
|
if (!uploadingNotesLock.try_lock()) {
|
||||||
|
// Do not run more than one uploading task at a time.
|
||||||
|
LOG(LDEBUG, ("OSM notes upload is already running"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::unique_lock<std::mutex> dataAccessLock(m_dataAccessMutex);
|
||||||
|
|
||||||
auto const doUpload = [self, auth]()
|
// Size of m_notes is decreased only in this method.
|
||||||
|
size_t size = m_notes.size();
|
||||||
|
osm::ServerApi06 api(auth);
|
||||||
|
|
||||||
|
while (size > 0)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> ulock(self->m_mu);
|
try
|
||||||
// Size of m_notes is decreased only in this method.
|
|
||||||
auto & notes = self->m_notes;
|
|
||||||
size_t size = notes.size();
|
|
||||||
osm::ServerApi06 api(auth);
|
|
||||||
|
|
||||||
while (size > 0)
|
|
||||||
{
|
{
|
||||||
try
|
dataAccessLock.unlock();
|
||||||
{
|
auto const id = api.CreateNote(m_notes.front().m_point, m_notes.front().m_note);
|
||||||
ulock.unlock();
|
dataAccessLock.lock();
|
||||||
auto const id = api.CreateNote(notes.front().m_point, notes.front().m_note);
|
LOG(LINFO, ("A note uploaded with id", id));
|
||||||
ulock.lock();
|
}
|
||||||
LOG(LINFO, ("A note uploaded with id", id));
|
catch (osm::ServerApi06::ServerApi06Exception const & e)
|
||||||
}
|
{
|
||||||
catch (osm::ServerApi06::ServerApi06Exception const & e)
|
LOG(LERROR, ("Can't upload note.", e.Msg()));
|
||||||
{
|
// Don't attempt upload for other notes as they will likely suffer from the same error.
|
||||||
LOG(LERROR, ("Can't upload note.", e.Msg()));
|
return;
|
||||||
// We believe that next iterations will suffer from the same error.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
notes.pop_front();
|
|
||||||
--size;
|
|
||||||
++self->m_uploadedNotesCount;
|
|
||||||
Save(self->m_fileName, self->m_notes, self->m_uploadedNotesCount);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
static auto future = std::async(std::launch::async, doUpload);
|
m_notes.pop_front();
|
||||||
auto const status = future.wait_for(std::chrono::milliseconds(0));
|
--size;
|
||||||
if (status == std::future_status::ready)
|
++m_uploadedNotesCount;
|
||||||
future = std::async(std::launch::async, doUpload);
|
Save(m_fileName, m_notes, m_uploadedNotesCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<Note> Notes::GetNotes() const
|
std::list<Note> Notes::GetNotes() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> g(m_mu);
|
std::lock_guard<std::mutex> g(m_dataAccessMutex);
|
||||||
return m_notes;
|
return m_notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Notes::NotUploadedNotesCount() const
|
size_t Notes::NotUploadedNotesCount() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> g(m_mu);
|
std::lock_guard<std::mutex> g(m_dataAccessMutex);
|
||||||
return m_notes.size();
|
return m_notes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Notes::UploadedNotesCount() const
|
size_t Notes::UploadedNotesCount() const
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> g(m_mu);
|
std::lock_guard<std::mutex> g(m_dataAccessMutex);
|
||||||
return m_uploadedNotesCount;
|
return m_uploadedNotesCount;
|
||||||
}
|
}
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ private:
|
|||||||
explicit Notes(std::string const & fileName);
|
explicit Notes(std::string const & fileName);
|
||||||
|
|
||||||
std::string const m_fileName;
|
std::string const m_fileName;
|
||||||
mutable std::mutex m_mu;
|
mutable std::mutex m_dataAccessMutex;
|
||||||
|
mutable std::mutex m_uploadingNotesMutex;
|
||||||
|
|
||||||
// m_notes keeps the notes that have not been uploaded yet.
|
// m_notes keeps the notes that have not been uploaded yet.
|
||||||
// Once a note has been uploaded, it is removed from m_notes.
|
// Once a note has been uploaded, it is removed from m_notes.
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ bool IsObsolete(editor::XMLFeature const & xml, FeatureID const & fid)
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Editor::Editor() : m_configLoader(m_config), m_notes(editor::Notes::MakeNotes()), m_isUploadingNow(false)
|
Editor::Editor() : m_configLoader(m_config), m_notes(editor::Notes::MakeNotes())
|
||||||
{
|
{
|
||||||
SetDefaultStorage();
|
SetDefaultStorage();
|
||||||
}
|
}
|
||||||
@@ -576,217 +576,165 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
|
|||||||
if (m_notes->NotUploadedNotesCount())
|
if (m_notes->NotUploadedNotesCount())
|
||||||
m_notes->Upload(OsmOAuth::ServerAuth(oauthToken));
|
m_notes->Upload(OsmOAuth::ServerAuth(oauthToken));
|
||||||
|
|
||||||
auto const features = m_features.Get();
|
if (!HaveMapEditsToUpload(*m_features.Get()))
|
||||||
|
|
||||||
if (!HaveMapEditsToUpload(*features))
|
|
||||||
{
|
{
|
||||||
LOG(LDEBUG, ("There are no local edits to upload."));
|
LOG(LDEBUG, ("There are no local edits to upload."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto upload = [this](string secret, ChangesetTags tags, FinishUploadCallback callback)
|
std::unique_lock<std::mutex> uploadingEditsLock(m_uploadingEditsMutex, std::defer_lock);
|
||||||
{
|
if (!uploadingEditsLock.try_lock()) {
|
||||||
int uploadedFeaturesCount = 0, errorsCount = 0;
|
// Do not run more than one uploading task at a time.
|
||||||
ChangesetWrapper changeset(secret, std::move(tags));
|
LOG(LDEBUG, ("OSM edits upload is already running"));
|
||||||
auto const features = m_features.Get();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto const & id : *features)
|
int uploadedFeaturesCount = 0, errorsCount = 0;
|
||||||
|
ChangesetWrapper changeset(oauthToken, std::move(tags));
|
||||||
|
auto const features = m_features.Get();
|
||||||
|
|
||||||
|
for (auto const & id : *features)
|
||||||
|
{
|
||||||
|
if (!id.first.IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (auto const & index : id.second)
|
||||||
{
|
{
|
||||||
if (!id.first.IsAlive())
|
FeatureTypeInfo const & fti = index.second;
|
||||||
|
// Do not process already uploaded features or those failed permanently.
|
||||||
|
if (!NeedsUpload(fti.m_uploadStatus))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (auto const & index : id.second)
|
// TODO(a): Use UploadInfo as part of FeatureTypeInfo.
|
||||||
|
UploadInfo uploadInfo = {fti.m_uploadAttemptTimestamp, fti.m_uploadStatus, fti.m_uploadError};
|
||||||
|
|
||||||
|
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
|
||||||
|
|
||||||
|
// Don't use new editor for Legacy Objects
|
||||||
|
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
|
||||||
|
bool useNewEditor =
|
||||||
|
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
FeatureTypeInfo const & fti = index.second;
|
if (useNewEditor)
|
||||||
// Do not process already uploaded features or those failed permanently.
|
|
||||||
if (!NeedsUpload(fti.m_uploadStatus))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO(a): Use UploadInfo as part of FeatureTypeInfo.
|
|
||||||
UploadInfo uploadInfo = {fti.m_uploadAttemptTimestamp, fti.m_uploadStatus, fti.m_uploadError};
|
|
||||||
|
|
||||||
LOG(LDEBUG, ("Content of editJournal:\n", fti.m_object.GetJournal().JournalToString()));
|
|
||||||
|
|
||||||
// Don't use new editor for Legacy Objects
|
|
||||||
auto const & journalHistory = fti.m_object.GetJournal().GetJournalHistory();
|
|
||||||
bool useNewEditor =
|
|
||||||
journalHistory.empty() || journalHistory.front().journalEntryType != JournalEntryType::LegacyObject;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (useNewEditor)
|
LOG(LDEBUG, ("New Editor used\n"));
|
||||||
|
|
||||||
|
switch (fti.m_status)
|
||||||
{
|
{
|
||||||
LOG(LDEBUG, ("New Editor used\n"));
|
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||||
|
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||||
switch (fti.m_status)
|
case FeatureStatus::Created: // fallthrough
|
||||||
{
|
case FeatureStatus::Modified:
|
||||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
|
||||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
|
||||||
case FeatureStatus::Created: // fallthrough
|
|
||||||
case FeatureStatus::Modified:
|
|
||||||
{
|
|
||||||
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
|
||||||
|
|
||||||
switch (fti.m_object.GetEditingLifecycle())
|
|
||||||
{
|
|
||||||
case EditingLifecycle::CREATED:
|
|
||||||
{
|
|
||||||
// Generate XMLFeature for new object
|
|
||||||
JournalEntry const & createEntry = journal.front();
|
|
||||||
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
|
|
||||||
("First item should have type ObjectCreated"));
|
|
||||||
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
|
|
||||||
XMLFeature feature =
|
|
||||||
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
|
|
||||||
|
|
||||||
// Check if place already exists
|
|
||||||
bool mergeSameLocation = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
|
||||||
if (objCreateData.mercator == osmFeature.GetMercatorCenter())
|
|
||||||
{
|
|
||||||
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
|
||||||
feature = osmFeature;
|
|
||||||
mergeSameLocation = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
|
||||||
{}
|
|
||||||
catch (ChangesetWrapper::EmptyFeatureException const &)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Add tags to XMLFeature
|
|
||||||
UpdateXMLFeatureTags(feature, journal);
|
|
||||||
|
|
||||||
// Upload XMLFeature to OSM
|
|
||||||
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
|
|
||||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
|
||||||
if (!mergeSameLocation)
|
|
||||||
changeset.Create(feature);
|
|
||||||
else
|
|
||||||
changeset.Modify(feature);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditingLifecycle::MODIFIED:
|
|
||||||
{
|
|
||||||
// Load existing OSM object (Throws, see catch below)
|
|
||||||
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
|
|
||||||
|
|
||||||
// Update tags of XMLFeature
|
|
||||||
UpdateXMLFeatureTags(feature, journal);
|
|
||||||
|
|
||||||
// Upload XMLFeature to OSM
|
|
||||||
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
|
|
||||||
changeset.AddChangesetTag("info:new_editor", "yes");
|
|
||||||
changeset.Modify(feature);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditingLifecycle::IN_SYNC:
|
|
||||||
{
|
|
||||||
CHECK(false, ("Object already IN_SYNC should not be here"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FeatureStatus::Deleted:
|
|
||||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
|
||||||
if (!originalObjectPtr)
|
|
||||||
{
|
|
||||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
|
||||||
GetPlatform().RunTask(Platform::Thread::Gui,
|
|
||||||
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // Use old editor
|
|
||||||
{
|
{
|
||||||
// Todo: Remove old editor after transition period
|
std::list<JournalEntry> const & journal = fti.m_object.GetJournal().GetJournal();
|
||||||
switch (fti.m_status)
|
|
||||||
{
|
|
||||||
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
|
||||||
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
|
||||||
case FeatureStatus::Created:
|
|
||||||
{
|
|
||||||
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
|
||||||
if (!fti.m_street.empty())
|
|
||||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
|
||||||
|
|
||||||
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
switch (fti.m_object.GetEditingLifecycle())
|
||||||
("Linear and area features creation is not supported yet."));
|
{
|
||||||
|
case EditingLifecycle::CREATED:
|
||||||
|
{
|
||||||
|
// Generate XMLFeature for new object
|
||||||
|
JournalEntry const & createEntry = journal.front();
|
||||||
|
ASSERT(createEntry.journalEntryType == JournalEntryType::ObjectCreated,
|
||||||
|
("First item should have type ObjectCreated"));
|
||||||
|
ObjCreateData const & objCreateData = std::get<ObjCreateData>(createEntry.data);
|
||||||
|
XMLFeature feature =
|
||||||
|
editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator);
|
||||||
|
|
||||||
|
// Check if place already exists
|
||||||
|
bool mergeSameLocation = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
auto const center = fti.m_object.GetMercator();
|
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator);
|
||||||
// Throws, see catch below.
|
if (objCreateData.mercator == osmFeature.GetMercatorCenter())
|
||||||
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
|
||||||
|
|
||||||
// If we are here, it means that object already exists at the given point.
|
|
||||||
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
|
||||||
XMLFeature const osmFeatureCopy = osmFeature;
|
|
||||||
osmFeature.ApplyPatch(feature);
|
|
||||||
// Check to avoid uploading duplicates into OSM.
|
|
||||||
if (osmFeature == osmFeatureCopy)
|
|
||||||
{
|
{
|
||||||
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
changeset.AddChangesetTag("info:merged_same_location", "yes");
|
||||||
// Don't delete this local change right now for user to see it in profile.
|
feature = osmFeature;
|
||||||
// It will be automatically deleted by migration code on the next maps update.
|
mergeSameLocation = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
changeset.AddChangesetTag("info:feature_close_by", "yes");
|
||||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
|
||||||
changeset.AddChangesetTag("info:features_merged", "yes");
|
|
||||||
changeset.Modify(osmFeature);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||||
{
|
{}
|
||||||
// Object was never created by anyone else - it's safe to create it.
|
|
||||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
|
||||||
changeset.Create(feature);
|
|
||||||
}
|
|
||||||
catch (ChangesetWrapper::EmptyFeatureException const &)
|
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||||
{
|
{}
|
||||||
// There is another node nearby, but it should be safe to create a new one.
|
|
||||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
// Add tags to XMLFeature
|
||||||
|
UpdateXMLFeatureTags(feature, journal);
|
||||||
|
|
||||||
|
// Upload XMLFeature to OSM
|
||||||
|
LOG(LDEBUG, ("CREATE Feature (newEditor)", feature));
|
||||||
|
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||||
|
if (!mergeSameLocation)
|
||||||
changeset.Create(feature);
|
changeset.Create(feature);
|
||||||
}
|
else
|
||||||
catch (...)
|
changeset.Modify(feature);
|
||||||
{
|
break;
|
||||||
// Pass network or other errors to outside exception handler.
|
}
|
||||||
throw;
|
|
||||||
}
|
case EditingLifecycle::MODIFIED:
|
||||||
|
{
|
||||||
|
// Load existing OSM object (Throws, see catch below)
|
||||||
|
XMLFeature feature = GetMatchingFeatureFromOSM(changeset, fti.m_object);
|
||||||
|
|
||||||
|
// Update tags of XMLFeature
|
||||||
|
UpdateXMLFeatureTags(feature, journal);
|
||||||
|
|
||||||
|
// Upload XMLFeature to OSM
|
||||||
|
LOG(LDEBUG, ("MODIFIED Feature (newEditor)", feature));
|
||||||
|
changeset.AddChangesetTag("info:new_editor", "yes");
|
||||||
|
changeset.Modify(feature);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EditingLifecycle::IN_SYNC:
|
||||||
|
{
|
||||||
|
CHECK(false, ("Object already IN_SYNC should not be here"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case FeatureStatus::Modified:
|
case FeatureStatus::Deleted:
|
||||||
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
|
if (!originalObjectPtr)
|
||||||
{
|
{
|
||||||
// Do not serialize feature's type to avoid breaking OSM data.
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
// TODO: Implement correct types matching when we support modifying existing feature types.
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
XMLFeature feature = editor::ToXML(fti.m_object, false);
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
if (!fti.m_street.empty())
|
continue;
|
||||||
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
}
|
||||||
|
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // Use old editor
|
||||||
|
{
|
||||||
|
// Todo: Remove old editor after transition period
|
||||||
|
switch (fti.m_status)
|
||||||
|
{
|
||||||
|
case FeatureStatus::Untouched: CHECK(false, ("It's impossible.")); continue;
|
||||||
|
case FeatureStatus::Obsolete: continue; // Obsolete features will be deleted by OSMers.
|
||||||
|
case FeatureStatus::Created:
|
||||||
|
{
|
||||||
|
XMLFeature feature = editor::ToXML(fti.m_object, true);
|
||||||
|
if (!fti.m_street.empty())
|
||||||
|
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||||
|
|
||||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node,
|
||||||
if (!originalObjectPtr)
|
("Linear and area features creation is not supported yet."));
|
||||||
{
|
try
|
||||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
{
|
||||||
GetPlatform().RunTask(Platform::Thread::Gui,
|
auto const center = fti.m_object.GetMercator();
|
||||||
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
// Throws, see catch below.
|
||||||
continue;
|
XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(center);
|
||||||
}
|
|
||||||
|
|
||||||
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
|
// If we are here, it means that object already exists at the given point.
|
||||||
|
// To avoid nodes duplication, merge and apply changes to it instead of creating a new one.
|
||||||
XMLFeature const osmFeatureCopy = osmFeature;
|
XMLFeature const osmFeatureCopy = osmFeature;
|
||||||
osmFeature.ApplyPatch(feature);
|
osmFeature.ApplyPatch(feature);
|
||||||
// Check to avoid uploading duplicates into OSM.
|
// Check to avoid uploading duplicates into OSM.
|
||||||
@@ -798,86 +746,129 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature));
|
||||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.AddChangesetTag("info:features_merged", "yes");
|
||||||
changeset.Modify(osmFeature);
|
changeset.Modify(osmFeature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
catch (ChangesetWrapper::OsmObjectWasDeletedException const &)
|
||||||
|
{
|
||||||
case FeatureStatus::Deleted:
|
// Object was never created by anyone else - it's safe to create it.
|
||||||
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
|
||||||
if (!originalObjectPtr)
|
|
||||||
{
|
|
||||||
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
|
||||||
GetPlatform().RunTask(Platform::Thread::Gui,
|
|
||||||
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
changeset.AddChangesetTag("info:old_editor", "yes");
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
changeset.Create(feature);
|
||||||
break;
|
}
|
||||||
|
catch (ChangesetWrapper::EmptyFeatureException const &)
|
||||||
|
{
|
||||||
|
// There is another node nearby, but it should be safe to create a new one.
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Create(feature);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Pass network or other errors to outside exception handler.
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploadInfo.m_uploadStatus = kUploaded;
|
break;
|
||||||
uploadInfo.m_uploadError.clear();
|
|
||||||
++uploadedFeaturesCount;
|
|
||||||
}
|
|
||||||
catch (ChangesetWrapper::OsmObjectWasDeletedException const & ex)
|
|
||||||
{
|
|
||||||
uploadInfo.m_uploadStatus = kDeletedFromOSMServer;
|
|
||||||
uploadInfo.m_uploadError = ex.Msg();
|
|
||||||
++errorsCount;
|
|
||||||
LOG(LWARNING, (ex.what()));
|
|
||||||
changeset.AddToChangesetKeyList("upload_attempt_error", kDeletedFromOSMServer);
|
|
||||||
}
|
|
||||||
catch (ChangesetWrapper::EmptyFeatureException const & ex)
|
|
||||||
{
|
|
||||||
uploadInfo.m_uploadStatus = kMatchedFeatureIsEmpty;
|
|
||||||
uploadInfo.m_uploadError = ex.Msg();
|
|
||||||
++errorsCount;
|
|
||||||
LOG(LWARNING, (ex.what()));
|
|
||||||
changeset.AddToChangesetKeyList("upload_attempt_error", kMatchedFeatureIsEmpty);
|
|
||||||
}
|
|
||||||
catch (RootException const & ex)
|
|
||||||
{
|
|
||||||
uploadInfo.m_uploadStatus = kNeedsRetry;
|
|
||||||
uploadInfo.m_uploadError = ex.Msg();
|
|
||||||
++errorsCount;
|
|
||||||
LOG(LWARNING, (ex.what()));
|
|
||||||
changeset.AddToChangesetKeyList("upload_attempt_error", ex.Msg());
|
|
||||||
}
|
|
||||||
// TODO(AlexZ): Use timestamp from the server.
|
|
||||||
uploadInfo.m_uploadAttemptTimestamp = time(nullptr);
|
|
||||||
|
|
||||||
GetPlatform().RunTask(Platform::Thread::Gui, [this, id = fti.m_object.GetID(), uploadInfo]()
|
case FeatureStatus::Modified:
|
||||||
{
|
{
|
||||||
// Call Save every time we modify each feature's information.
|
// Do not serialize feature's type to avoid breaking OSM data.
|
||||||
SaveUploadedInformation(id, uploadInfo);
|
// TODO: Implement correct types matching when we support modifying existing feature types.
|
||||||
});
|
XMLFeature feature = editor::ToXML(fti.m_object, false);
|
||||||
|
if (!fti.m_street.empty())
|
||||||
|
feature.SetTagValue(kAddrStreetTag, fti.m_street);
|
||||||
|
|
||||||
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
|
if (!originalObjectPtr)
|
||||||
|
{
|
||||||
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLFeature osmFeature = GetMatchingFeatureFromOSM(changeset, *originalObjectPtr);
|
||||||
|
XMLFeature const osmFeatureCopy = osmFeature;
|
||||||
|
osmFeature.ApplyPatch(feature);
|
||||||
|
// Check to avoid uploading duplicates into OSM.
|
||||||
|
if (osmFeature == osmFeatureCopy)
|
||||||
|
{
|
||||||
|
LOG(LWARNING, ("Local changes are equal to OSM, feature has not been uploaded.", osmFeatureCopy));
|
||||||
|
// Don't delete this local change right now for user to see it in profile.
|
||||||
|
// It will be automatically deleted by migration code on the next maps update.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG(LDEBUG, ("Uploading patched feature", osmFeature));
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Modify(osmFeature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FeatureStatus::Deleted:
|
||||||
|
auto const originalObjectPtr = GetOriginalMapObject(fti.m_object.GetID());
|
||||||
|
if (!originalObjectPtr)
|
||||||
|
{
|
||||||
|
LOG(LERROR, ("A feature with id", fti.m_object.GetID(), "cannot be loaded."));
|
||||||
|
GetPlatform().RunTask(Platform::Thread::Gui,
|
||||||
|
[this, fid = fti.m_object.GetID()]() { RemoveFeatureIfExists(fid); });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
changeset.AddChangesetTag("info:old_editor", "yes");
|
||||||
|
changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploadInfo.m_uploadStatus = kUploaded;
|
||||||
|
uploadInfo.m_uploadError.clear();
|
||||||
|
++uploadedFeaturesCount;
|
||||||
}
|
}
|
||||||
|
catch (ChangesetWrapper::OsmObjectWasDeletedException const & ex)
|
||||||
|
{
|
||||||
|
uploadInfo.m_uploadStatus = kDeletedFromOSMServer;
|
||||||
|
uploadInfo.m_uploadError = ex.Msg();
|
||||||
|
++errorsCount;
|
||||||
|
LOG(LWARNING, (ex.what()));
|
||||||
|
changeset.AddToChangesetKeyList("upload_attempt_error", kDeletedFromOSMServer);
|
||||||
|
}
|
||||||
|
catch (ChangesetWrapper::EmptyFeatureException const & ex)
|
||||||
|
{
|
||||||
|
uploadInfo.m_uploadStatus = kMatchedFeatureIsEmpty;
|
||||||
|
uploadInfo.m_uploadError = ex.Msg();
|
||||||
|
++errorsCount;
|
||||||
|
LOG(LWARNING, (ex.what()));
|
||||||
|
changeset.AddToChangesetKeyList("upload_attempt_error", kMatchedFeatureIsEmpty);
|
||||||
|
}
|
||||||
|
catch (RootException const & ex)
|
||||||
|
{
|
||||||
|
uploadInfo.m_uploadStatus = kNeedsRetry;
|
||||||
|
uploadInfo.m_uploadError = ex.Msg();
|
||||||
|
++errorsCount;
|
||||||
|
LOG(LWARNING, (ex.what()));
|
||||||
|
changeset.AddToChangesetKeyList("upload_attempt_error", ex.Msg());
|
||||||
|
}
|
||||||
|
// TODO(AlexZ): Use timestamp from the server.
|
||||||
|
uploadInfo.m_uploadAttemptTimestamp = time(nullptr);
|
||||||
|
|
||||||
|
GetPlatform().RunTask(Platform::Thread::Gui, [this, id = fti.m_object.GetID(), uploadInfo]()
|
||||||
|
{
|
||||||
|
// Call Save every time we modify each feature's information.
|
||||||
|
SaveUploadedInformation(id, uploadInfo);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (callback)
|
if (callback)
|
||||||
{
|
|
||||||
UploadResult result = UploadResult::NothingToUpload;
|
|
||||||
if (uploadedFeaturesCount)
|
|
||||||
result = UploadResult::Success;
|
|
||||||
else if (errorsCount)
|
|
||||||
result = UploadResult::Error;
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_isUploadingNow = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Do not run more than one uploading task at a time.
|
|
||||||
if (!m_isUploadingNow)
|
|
||||||
{
|
{
|
||||||
m_isUploadingNow = true;
|
UploadResult result = UploadResult::NothingToUpload;
|
||||||
GetPlatform().RunTask(Platform::Thread::Network, [upload = std::move(upload), oauthToken, tags = std::move(tags),
|
if (uploadedFeaturesCount)
|
||||||
callback = std::move(callback)]()
|
result = UploadResult::Success;
|
||||||
{ upload(std::move(oauthToken), std::move(tags), std::move(callback)); });
|
else if (errorsCount)
|
||||||
|
result = UploadResult::Error;
|
||||||
|
callback(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ private:
|
|||||||
|
|
||||||
std::unique_ptr<editor::StorageBase> m_storage;
|
std::unique_ptr<editor::StorageBase> m_storage;
|
||||||
|
|
||||||
std::atomic<bool> m_isUploadingNow;
|
std::mutex m_uploadingEditsMutex;
|
||||||
|
|
||||||
DECLARE_THREAD_CHECKER(MainThreadChecker);
|
DECLARE_THREAD_CHECKER(MainThreadChecker);
|
||||||
}; // class Editor
|
}; // class Editor
|
||||||
|
|||||||
Reference in New Issue
Block a user