FeatureID MigrateFeatureIndex(osm::Editor::ForEachFeaturesNearByFn & forEach, XMLFeature const & xml, FeatureStatus const featureStatus, GenerateIDFn const & generateID) { switch (xml.GetType()) { case XMLFeature::Type::Unknown: MYTHROW(MigrationError, ("Migration for XMLFeature::Type::Unknown is not possible")); case XMLFeature::Type::Node: return MigrateNodeFeatureIndex(forEach, xml, featureStatus, generateID); case XMLFeature::Type::Way: case XMLFeature::Type::Relation: return MigrateWayOrRelatonFeatureIndex(forEach, xml, featureStatus, generateID); } UNREACHABLE(); }
void Editor::UploadChanges(string const & key, string const & secret, TChangesetTags tags, TFinishUploadCallback callBack) { if (m_notes->NotUploadedNotesCount()) UploadNotes(key, secret); if (!HaveMapEditsToUpload()) { LOG(LDEBUG, ("There are no local edits to upload.")); return; } alohalytics::LogEvent("Editor_DataSync_started"); // TODO(AlexZ): features access should be synchronized. auto const upload = [this](string key, string secret, TChangesetTags tags, TFinishUploadCallback callBack) { // This lambda was designed to start after app goes into background. But for cases when user is immediately // coming back to the app we work with a copy, because 'for' loops below can take a significant amount of time. auto features = m_features; int uploadedFeaturesCount = 0, errorsCount = 0; ChangesetWrapper changeset({key, secret}, tags); for (auto & id : features) { for (auto & index : id.second) { FeatureTypeInfo & fti = index.second; // Do not process already uploaded features or those failed permanently. if (!NeedsUpload(fti.m_uploadStatus)) continue; string ourDebugFeatureString; try { 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 = fti.m_feature.ToXML(true); if (!fti.m_street.empty()) feature.SetTagValue(kAddrStreetTag, fti.m_street); ourDebugFeatureString = DebugPrint(feature); ASSERT_EQUAL(feature.GetType(), XMLFeature::Type::Node, ("Linear and area features creation is not supported yet.")); try { XMLFeature osmFeature = changeset.GetMatchingNodeFeatureFromOSM(fti.m_feature.GetCenter()); // 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 an 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)); // 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, ("Create case: uploading patched feature", osmFeature)); changeset.Modify(osmFeature); } } catch (ChangesetWrapper::OsmObjectWasDeletedException const &) { // Object was never created by anyone else - it's safe to create it. changeset.Create(feature); } catch (ChangesetWrapper::EmptyFeatureException const &) { // There is another node nearby, but it should be safe to create a new one. changeset.Create(feature); } catch (...) { // Pass network or other errors to outside exception handler. throw; } } break; 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 = fti.m_feature.ToXML(false); if (!fti.m_street.empty()) feature.SetTagValue(kAddrStreetTag, fti.m_street); ourDebugFeatureString = DebugPrint(feature); auto const originalFeaturePtr = GetOriginalFeature(fti.m_feature.GetID()); if (!originalFeaturePtr) { LOG(LERROR, ("A feature with id", fti.m_feature.GetID(), "cannot be loaded.")); alohalytics::LogEvent("Editor_MissingFeature_Error"); RemoveFeatureFromStorageIfExists(fti.m_feature.GetID()); continue; } XMLFeature osmFeature = GetMatchingFeatureFromOSM( changeset, *originalFeaturePtr); 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.Modify(osmFeature); } } break; case FeatureStatus::Deleted: auto const originalFeaturePtr = GetOriginalFeature(fti.m_feature.GetID()); if (!originalFeaturePtr) { LOG(LERROR, ("A feature with id", fti.m_feature.GetID(), "cannot be loaded.")); alohalytics::LogEvent("Editor_MissingFeature_Error"); RemoveFeatureFromStorageIfExists(fti.m_feature.GetID()); continue; } changeset.Delete(GetMatchingFeatureFromOSM( changeset, *originalFeaturePtr)); break; } fti.m_uploadStatus = kUploaded; fti.m_uploadError.clear(); ++uploadedFeaturesCount; } catch (ChangesetWrapper::OsmObjectWasDeletedException const & ex) { fti.m_uploadStatus = kDeletedFromOSMServer; fti.m_uploadError = ex.what(); ++errorsCount; LOG(LWARNING, (ex.what())); } catch (ChangesetWrapper::RelationFeatureAreNotSupportedException const & ex) { fti.m_uploadStatus = kRelationsAreNotSupported; fti.m_uploadError = ex.what(); ++errorsCount; LOG(LWARNING, (ex.what())); } catch (ChangesetWrapper::EmptyFeatureException const & ex) { fti.m_uploadStatus = kWrongMatch; fti.m_uploadError = ex.what(); ++errorsCount; LOG(LWARNING, (ex.what())); } catch (RootException const & ex) { fti.m_uploadStatus = kNeedsRetry; fti.m_uploadError = ex.what(); ++errorsCount; LOG(LWARNING, (ex.what())); } // TODO(AlexZ): Use timestamp from the server. fti.m_uploadAttemptTimestamp = time(nullptr); if (fti.m_uploadStatus != kUploaded) { ms::LatLon const ll = MercatorBounds::ToLatLon(feature::GetCenter(fti.m_feature)); alohalytics::LogEvent("Editor_DataSync_error", {{"type", fti.m_uploadStatus}, {"details", fti.m_uploadError}, {"our", ourDebugFeatureString}, {"mwm", fti.m_feature.GetID().GetMwmName()}, {"mwm_version", strings::to_string(fti.m_feature.GetID().GetMwmVersion())} }, alohalytics::Location::FromLatLon(ll.lat, ll.lon)); } // Call Save every time we modify each feature's information. SaveUploadedInformation(fti); } } alohalytics::LogEvent("Editor_DataSync_finished", {{"errors", strings::to_string(errorsCount)}, {"uploaded", strings::to_string(uploadedFeaturesCount)}, {"changeset", strings::to_string(changeset.GetChangesetId())} }); if (callBack) { UploadResult result = UploadResult::NothingToUpload; if (uploadedFeaturesCount) result = UploadResult::Success; else if (errorsCount) result = UploadResult::Error; callBack(result); } }; // Do not run more than one upload thread at a time. static auto future = async(launch::async, upload, key, secret, tags, callBack); auto const status = future.wait_for(milliseconds(0)); if (status == future_status::ready) future = async(launch::async, upload, key, secret, tags, callBack); }