diff --git a/libs/editor/editor_notes.cpp b/libs/editor/editor_notes.cpp index 3bc1f4a21..8ce1cf710 100644 --- a/libs/editor/editor_notes.cpp +++ b/libs/editor/editor_notes.cpp @@ -142,7 +142,7 @@ void Notes::CreateNote(ms::LatLon const & latLon, std::string const & text) return; } - std::lock_guard g(m_mu); + std::lock_guard g(m_dataAccessMutex); 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; }); // 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) { - // Capture self to keep it from destruction until this thread is done. - auto const self = shared_from_this(); + std::unique_lock uploadingNotesLock(m_uploadingNotesMutex, std::defer_lock); + 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 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 ulock(self->m_mu); - // 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 { - try - { - ulock.unlock(); - auto const id = api.CreateNote(notes.front().m_point, notes.front().m_note); - ulock.lock(); - LOG(LINFO, ("A note uploaded with id", id)); - } - catch (osm::ServerApi06::ServerApi06Exception const & e) - { - LOG(LERROR, ("Can't upload note.", e.Msg())); - // 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); + dataAccessLock.unlock(); + auto const id = api.CreateNote(m_notes.front().m_point, m_notes.front().m_note); + dataAccessLock.lock(); + LOG(LINFO, ("A note uploaded with id", id)); + } + 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. + return; } - }; - static auto future = std::async(std::launch::async, doUpload); - auto const status = future.wait_for(std::chrono::milliseconds(0)); - if (status == std::future_status::ready) - future = std::async(std::launch::async, doUpload); + m_notes.pop_front(); + --size; + ++m_uploadedNotesCount; + Save(m_fileName, m_notes, m_uploadedNotesCount); + } } std::list Notes::GetNotes() const { - std::lock_guard g(m_mu); + std::lock_guard g(m_dataAccessMutex); return m_notes; } size_t Notes::NotUploadedNotesCount() const { - std::lock_guard g(m_mu); + std::lock_guard g(m_dataAccessMutex); return m_notes.size(); } size_t Notes::UploadedNotesCount() const { - std::lock_guard g(m_mu); + std::lock_guard g(m_dataAccessMutex); return m_uploadedNotesCount; } } // namespace editor diff --git a/libs/editor/editor_notes.hpp b/libs/editor/editor_notes.hpp index a59477efd..df4788f7e 100644 --- a/libs/editor/editor_notes.hpp +++ b/libs/editor/editor_notes.hpp @@ -46,7 +46,8 @@ private: explicit Notes(std::string const & 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. // Once a note has been uploaded, it is removed from m_notes. diff --git a/libs/editor/osm_editor.cpp b/libs/editor/osm_editor.cpp index c79689c5e..97b6b3425 100644 --- a/libs/editor/osm_editor.cpp +++ b/libs/editor/osm_editor.cpp @@ -126,7 +126,7 @@ bool IsObsolete(editor::XMLFeature const & xml, FeatureID const & fid) } } // 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(); } @@ -576,217 +576,165 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish if (m_notes->NotUploadedNotesCount()) m_notes->Upload(OsmOAuth::ServerAuth(oauthToken)); - auto const features = m_features.Get(); - - if (!HaveMapEditsToUpload(*features)) + if (!HaveMapEditsToUpload(*m_features.Get())) { LOG(LDEBUG, ("There are no local edits to upload.")); return; } - auto upload = [this](string secret, ChangesetTags tags, FinishUploadCallback callback) - { - int uploadedFeaturesCount = 0, errorsCount = 0; - ChangesetWrapper changeset(secret, std::move(tags)); - auto const features = m_features.Get(); + std::unique_lock uploadingEditsLock(m_uploadingEditsMutex, std::defer_lock); + if (!uploadingEditsLock.try_lock()) { + // Do not run more than one uploading task at a time. + LOG(LDEBUG, ("OSM edits upload is already running")); + 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; - 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; - // 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) { - if (useNewEditor) + LOG(LDEBUG, ("New Editor used\n")); + + switch (fti.m_status) { - LOG(LDEBUG, ("New Editor used\n")); - - 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: // fallthrough - case FeatureStatus::Modified: - { - std::list 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(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 + 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: { - // 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); + std::list const & journal = fti.m_object.GetJournal().GetJournal(); - ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node, - ("Linear and area features creation is not supported yet.")); + 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(createEntry.data); + XMLFeature feature = + editor::TypeToXML(objCreateData.type, objCreateData.geomType, objCreateData.mercator); + + // Check if place already exists + bool mergeSameLocation = false; try { - auto const center = fti.m_object.GetMercator(); - // Throws, see catch below. - 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) + XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(objCreateData.mercator); + if (objCreateData.mercator == osmFeature.GetMercatorCenter()) { - 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. + changeset.AddChangesetTag("info:merged_same_location", "yes"); + feature = osmFeature; + mergeSameLocation = true; } else { - LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature)); - changeset.AddChangesetTag("info:old_editor", "yes"); - changeset.AddChangesetTag("info:features_merged", "yes"); - changeset.Modify(osmFeature); + changeset.AddChangesetTag("info:feature_close_by", "yes"); } } 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 &) - { - // 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); - } - catch (...) - { - // Pass network or other errors to outside exception handler. - throw; - } + 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::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. - // 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); + 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 + 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()); - 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; - } + ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node, + ("Linear and area features creation is not supported yet.")); + try + { + auto const center = fti.m_object.GetMercator(); + // Throws, see catch below. + 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; osmFeature.ApplyPatch(feature); // Check to avoid uploading duplicates into OSM. @@ -798,86 +746,129 @@ void Editor::UploadChanges(string const & oauthToken, ChangesetTags tags, Finish } else { - LOG(LDEBUG, ("Uploading patched feature", osmFeature)); + LOG(LDEBUG, ("Create case: uploading patched feature", osmFeature)); changeset.AddChangesetTag("info:old_editor", "yes"); + changeset.AddChangesetTag("info:features_merged", "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; - } + catch (ChangesetWrapper::OsmObjectWasDeletedException const &) + { + // Object was never created by anyone else - it's safe to create it. changeset.AddChangesetTag("info:old_editor", "yes"); - changeset.Delete(GetMatchingFeatureFromOSM(changeset, *originalObjectPtr)); - break; + changeset.Create(feature); + } + 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; - 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); + break; - 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); - }); + case FeatureStatus::Modified: + { + // Do not serialize feature's type to avoid breaking OSM data. + // 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) - { - 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) + if (callback) { - m_isUploadingNow = true; - GetPlatform().RunTask(Platform::Thread::Network, [upload = std::move(upload), oauthToken, tags = std::move(tags), - callback = std::move(callback)]() - { upload(std::move(oauthToken), std::move(tags), std::move(callback)); }); + UploadResult result = UploadResult::NothingToUpload; + if (uploadedFeaturesCount) + result = UploadResult::Success; + else if (errorsCount) + result = UploadResult::Error; + callback(result); } } diff --git a/libs/editor/osm_editor.hpp b/libs/editor/osm_editor.hpp index b0306d277..f412579bf 100644 --- a/libs/editor/osm_editor.hpp +++ b/libs/editor/osm_editor.hpp @@ -260,7 +260,7 @@ private: std::unique_ptr m_storage; - std::atomic m_isUploadingNow; + std::mutex m_uploadingEditsMutex; DECLARE_THREAD_CHECKER(MainThreadChecker); }; // class Editor