From 753499146c8f8365abe4f3018171d8f0c3188819 Mon Sep 17 00:00:00 2001 From: Daria Volvenkova Date: Mon, 28 Aug 2017 15:07:27 +0300 Subject: [PATCH] Locals API. First iteration. --- map/framework.cpp | 24 +++ map/framework.hpp | 5 + map/place_page_info.hpp | 16 ++ partners_api/CMakeLists.txt | 2 + partners_api/locals_api.cpp | 174 ++++++++++++++++++ partners_api/locals_api.hpp | 64 +++++++ partners_api/partners_api.pro | 2 + .../partners_api.xcodeproj/project.pbxproj | 8 + 8 files changed, 295 insertions(+) create mode 100644 partners_api/locals_api.cpp create mode 100644 partners_api/locals_api.hpp diff --git a/map/framework.cpp b/map/framework.cpp index 602ce0094b..87adbe3efa 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -552,6 +552,15 @@ cian::Api * Framework::GetCianApi(platform::NetworkPolicy const & policy) return nullptr; } +locals::Api * Framework::GetLocalsApi(platform::NetworkPolicy const & policy) +{ + ASSERT(m_localsApi, ()); + if (policy.CanUse()) + return m_localsApi.get(); + + return nullptr; +} + void Framework::ShowNode(storage::TCountryId const & countryId) { StopLocationFollow(); @@ -891,6 +900,8 @@ void Framework::FillInfoFromFeatureType(FeatureType const & ft, place_page::Info info.SetPreviewIsExtended(); } + FillLocalExperts(ft, info); + auto const mwmInfo = ft.GetID().m_mwmId.GetInfo(); bool const isMapVersionEditable = mwmInfo && mwmInfo->m_version.IsEditableMap(); bool const canEditOrAdd = featureStatus != osm::Editor::FeatureStatus::Obsolete && CanEditMap() && @@ -3423,3 +3434,16 @@ void Framework::InjectViator(place_page::Info & info) }, rect, scales::GetUpperScale(), mwmId); } + +void Framework::FillLocalExperts(FeatureType const & ft, place_page::Info & info) const +{ + if (GetDrawScale() > scales::GetUpperWorldScale() || + !ftypes::IsCityChecker::Instance()(ft)) + { + info.SetLocalsStatus(place_page::LocalsStatus::NotAvailable); + return; + } + + info.SetLocalsStatus(place_page::LocalsStatus::Available); + info.SetLocalsPageUrl(locals::Api::GetLocalsPageUrl()); +} diff --git a/map/framework.hpp b/map/framework.hpp index e2f33fe291..1be36f1ac4 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -46,6 +46,7 @@ #include "partners_api/booking_api.hpp" #include "partners_api/cian_api.hpp" +#include "partners_api/locals_api.hpp" #include "partners_api/taxi_engine.hpp" #include "partners_api/viator_api.hpp" @@ -177,6 +178,7 @@ protected: unique_ptr m_bookingApi = make_unique(); unique_ptr m_viatorApi = make_unique(); unique_ptr m_cianApi = make_unique(); + unique_ptr m_localsApi = make_unique(); df::DrapeApi m_drapeApi; @@ -218,6 +220,7 @@ public: viator::Api * GetViatorApi(platform::NetworkPolicy const & policy); taxi::Engine * GetTaxiEngine(platform::NetworkPolicy const & policy); cian::Api * GetCianApi(platform::NetworkPolicy const & policy); + locals::Api * GetLocalsApi(platform::NetworkPolicy const & policy); df::DrapeApi & GetDrapeApi() { return m_drapeApi; } @@ -831,4 +834,6 @@ private: /// Find feature with viator near point, provided in |info|, and inject viator data into |info|. void InjectViator(place_page::Info & info); + + void FillLocalExperts(FeatureType const & ft, place_page::Info & info) const; }; diff --git a/map/place_page_info.hpp b/map/place_page_info.hpp index 74b94018a8..f454e3a00c 100644 --- a/map/place_page_info.hpp +++ b/map/place_page_info.hpp @@ -46,6 +46,12 @@ enum class LocalAdsStatus Customer }; +enum class LocalsStatus +{ + NotAvailable, + Available +}; + class Info : public osm::MapObject { public: @@ -149,6 +155,12 @@ public: return m_reachableByProviders; } + /// Local experts + void SetLocalsStatus(LocalsStatus status) { m_localsStatus = status; } + LocalsStatus GetLocalsStatus() const { return m_localsStatus; } + void SetLocalsPageUrl(std::string const & url) { m_localsUrl = url; } + std::string const & GetLocalsPageUrl() const { return m_localsUrl; } + /// Local ads void SetLocalAdsStatus(LocalAdsStatus status) { m_localAdsStatus = status; } LocalAdsStatus GetLocalAdsStatus() const { return m_localAdsStatus; } @@ -251,5 +263,9 @@ private: /// Booking std::string m_bookingSearchUrl; + + /// Local experts + std::string m_localsUrl; + LocalsStatus m_localsStatus = LocalsStatus::NotAvailable; }; } // namespace place_page diff --git a/partners_api/CMakeLists.txt b/partners_api/CMakeLists.txt index cb4d3e3355..8af42c3e9a 100644 --- a/partners_api/CMakeLists.txt +++ b/partners_api/CMakeLists.txt @@ -17,6 +17,8 @@ set( facebook_ads.hpp google_ads.cpp google_ads.hpp + locals_api.cpp + locals_api.hpp mopub_ads.cpp mopub_ads.hpp opentable_api.cpp diff --git a/partners_api/locals_api.cpp b/partners_api/locals_api.cpp new file mode 100644 index 0000000000..2ee0f995be --- /dev/null +++ b/partners_api/locals_api.cpp @@ -0,0 +1,174 @@ +#include "partners_api/locals_api.hpp" + +#include "platform/http_client.hpp" +#include "platform/platform.hpp" +#include "platform/preferred_languages.hpp" + +#include "coding/multilang_utf8_string.hpp" +#include "coding/url_encode.hpp" + +#include "base/logging.hpp" +#include "base/string_utils.hpp" + +#include "3party/jansson/myjansson.hpp" + +#include "private.h" + +namespace +{ +using namespace locals; + +bool CheckJsonArray(json_t const * data) +{ + if (data == nullptr) + return false; + return json_is_array(data) && json_array_size(data) > 0; +} + +void ParseError(std::string const & src, ErrorCode & errorCode, std::string & message) +{ + message.clear(); + my::Json root(src.c_str()); + int code = 0; + FromJSONObject(root.get(), "code", code); + FromJSONObject(root.get(), "message", message); + // TODO(darina): Process real error codes. + errorCode = code > 0 ? ErrorCode::NoLocals : ErrorCode::RemoteError; +} + +void ParseLocals(std::string const & src, std::vector & locals, + bool & hasPrevious, bool & hasNext) +{ + locals.clear(); + my::Json root(src.c_str()); + auto previousField = my::GetJSONOptionalField(root.get(), "previous"); + auto nextField = my::GetJSONOptionalField(root.get(), "next"); + hasPrevious = json_is_number(previousField); + hasNext = json_is_number(nextField); + auto const results = json_object_get(root.get(), "results"); + if (!CheckJsonArray(results)) + return; + auto const dataSize = json_array_size(results); + for (size_t i = 0; i < dataSize; ++i) + { + LocalExpert local; + auto const item = json_array_get(results, i); + FromJSONObject(item, "id", local.m_id); + FromJSONObject(item, "motto", local.m_motto); + FromJSONObject(item, "link", local.m_pageUrl); + FromJSONObject(item, "photo", local.m_photoUrl); + FromJSONObject(item, "name", local.m_name); + FromJSONObject(item, "country", local.m_country); + FromJSONObject(item, "city", local.m_city); + FromJSONObject(item, "rating", local.m_rating); + FromJSONObject(item, "reviews", local.m_reviewCount); + FromJSONObject(item, "price_per_hour", local.m_pricePerHour); + FromJSONObject(item, "currency", local.m_currency); + FromJSONObject(item, "about_me", local.m_aboutExpert); + FromJSONObject(item, "i_will_show_you", local.m_offerDescription); + locals.push_back(move(local)); + } +} +} // namespace + +namespace locals +{ +bool RawApi::Get(double lat, double lon, std::string const & lang, size_t resultsOnPage, size_t pageNumber, + std::string & result) +{ + result.clear(); + std::ostringstream ostream; + ostream << LOCALS_API_URL << "/search?api_key=" << LOCALS_API_KEY + << "&lat=" << lat << "&lon=" << lon + << "&limit=" << resultsOnPage << "&page=" << pageNumber; + if (!lang.empty()) + ostream << "&lang=" << lang; + + platform::HttpClient request(ostream.str()); + request.SetHttpMethod("GET"); + if (request.RunHttpRequest() && request.ErrorCode() == 200) + { + result = request.ServerResponse(); + return true; + } + return false; +} + +std::string Api::GetLocalsPageUrl() +{ + return LOCALS_PAGE_URL; +} + +uint64_t Api::GetLocals(double lat, double lon, std::string const & lang, + size_t resultsOnPage, size_t pageNumber, + LocalsSuccessCallback const & successFn, + LocalsErrorCallback const & errorFn) +{ + uint64_t id = ++m_requestId; + GetPlatform().RunOnNetworkThread([id, lat, lon, lang, + resultsOnPage, pageNumber, successFn, errorFn]() + { + std::string result; + if (!RawApi::Get(lat, lon, lang, resultsOnPage, pageNumber, result)) + { + try + { + ErrorCode errorCode; + std::string errorMessage; + ParseError(result, errorCode, errorMessage); + LOG(LWARNING, ("Locals request failed:", errorCode, errorMessage)); + return errorFn(id, errorCode, errorMessage); + } + catch (my::Json::Exception const & e) + { + LOG(LWARNING, ("Unknown error:", e.Msg())); + return errorFn(id, ErrorCode::UnknownError, "Unknown error: " + e.Msg()); + } + } + + std::vector locals; + bool hasPreviousPage = false; + bool hasNextPage = false; + try + { + ParseLocals(result, locals, hasPreviousPage, hasNextPage); + } + catch (my::Json::Exception const & e) + { + LOG(LWARNING, ("Locals response parsing failed:", e.Msg())); + errorFn(id, ErrorCode::UnknownError, "Response parsing failed: " + e.Msg()); + } + + successFn(id, locals, pageNumber, resultsOnPage, hasPreviousPage, hasNextPage); + }); + return id; +} + +std::string DebugPrint(ErrorCode code) +{ + switch (code) + { + case ErrorCode::NoLocals: return "NoLocals"; + case ErrorCode::RemoteError: return "RemoteError"; + case ErrorCode::UnknownError: return "UnknownError"; + } + return "Unknown error code"; +} + +std::string DebugPrint(LocalExpert const & localExpert) +{ + std::ostringstream out; + out << "id: " << localExpert.m_id << std::endl + << "name: " << localExpert.m_name << std::endl + << "about: " << localExpert.m_aboutExpert << std::endl + << "city: " << localExpert.m_city << std::endl + << "country: " << localExpert.m_country << std::endl + << "currency: " << localExpert.m_currency << std::endl + << "pageUrl: " << localExpert.m_pageUrl << std::endl + << "photoUrl: " << localExpert.m_photoUrl << std::endl + << "description: " << localExpert.m_offerDescription << std::endl + << "motto: " << localExpert.m_motto << std::endl; + return out.str(); +} + +} // namespace locals diff --git a/partners_api/locals_api.hpp b/partners_api/locals_api.hpp new file mode 100644 index 0000000000..fc0e1d1b58 --- /dev/null +++ b/partners_api/locals_api.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "platform/platform.hpp" +#include "platform/safe_callback.hpp" + +#include + +#define STAGE_LOCALS_SERVER + +namespace locals +{ +class RawApi +{ +public: + static bool Get(double lat, double lon, std::string const & lang, + size_t resultsOnPage, size_t pageNumber, std::string & result); +}; + +enum class ErrorCode +{ + NoLocals, + RemoteError, + UnknownError +}; + +struct LocalExpert +{ + size_t m_id; + std::string m_name; + std::string m_country; + std::string m_city; + double m_rating; + size_t m_reviewCount; + double m_pricePerHour; + std::string m_currency; + std::string m_motto; + std::string m_aboutExpert; + std::string m_offerDescription; + std::string m_pageUrl; + std::string m_photoUrl; +}; + +using LocalsSuccessCallback = platform::SafeCallback const & locals, + size_t pageNumber, size_t countPerPage, + bool hasPreviousPage, bool hasNextPage)>; +using LocalsErrorCallback = platform::SafeCallback; + +class Api +{ +public: + static std::string GetLocalsPageUrl(); + uint64_t GetLocals(double lat, double lon, std::string const & lang, + size_t resultsOnPage, size_t pageNumber, + LocalsSuccessCallback const & successFn, + LocalsErrorCallback const & errorFn); +private: + // Id for currently processed request. + uint64_t m_requestId = 0; +}; + +std::string DebugPrint(ErrorCode code); +std::string DebugPrint(LocalExpert const & localExpert); +} // namespace locals diff --git a/partners_api/partners_api.pro b/partners_api/partners_api.pro index 2810ed4fed..b3e4a05238 100644 --- a/partners_api/partners_api.pro +++ b/partners_api/partners_api.pro @@ -15,6 +15,7 @@ SOURCES += \ cian_api.cpp \ facebook_ads.cpp \ google_ads.cpp \ + locals_api.cpp \ mopub_ads.cpp \ opentable_api.cpp \ rb_ads.cpp \ @@ -33,6 +34,7 @@ HEADERS += \ cian_api.hpp \ facebook_ads.hpp \ google_ads.hpp \ + locals_api.hpp \ mopub_ads.hpp \ opentable_api.hpp \ rb_ads.hpp \ diff --git a/xcode/partners_api/partners_api.xcodeproj/project.pbxproj b/xcode/partners_api/partners_api.xcodeproj/project.pbxproj index 825980e064..fe1e1916ca 100644 --- a/xcode/partners_api/partners_api.xcodeproj/project.pbxproj +++ b/xcode/partners_api/partners_api.xcodeproj/project.pbxproj @@ -46,6 +46,8 @@ 3DFEBF9F1EFBFC1500317D5C /* yandex_api.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DFEBF991EFBFC1500317D5C /* yandex_api.hpp */; }; 3DFEBFA31EFBFC2300317D5C /* taxi_engine_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DFEBFA01EFBFC2300317D5C /* taxi_engine_tests.cpp */; }; 3DFEBFA41EFBFC2300317D5C /* yandex_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DFEBFA11EFBFC2300317D5C /* yandex_tests.cpp */; }; + BB1956E61F543D7C003ECE6C /* locals_api.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BB1956E41F543D7B003ECE6C /* locals_api.cpp */; }; + BB1956E71F543D7C003ECE6C /* locals_api.hpp in Headers */ = {isa = PBXBuildFile; fileRef = BB1956E51F543D7C003ECE6C /* locals_api.hpp */; }; F67E75251DB8F06F00D6741F /* opentable_api.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F67E75231DB8F06F00D6741F /* opentable_api.cpp */; }; F67E75261DB8F06F00D6741F /* opentable_api.hpp in Headers */ = {isa = PBXBuildFile; fileRef = F67E75241DB8F06F00D6741F /* opentable_api.hpp */; }; F6B536401DA520E40067EEA5 /* booking_api.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6B5363C1DA520E40067EEA5 /* booking_api.cpp */; }; @@ -107,6 +109,8 @@ 3DFEBF991EFBFC1500317D5C /* yandex_api.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = yandex_api.hpp; sourceTree = ""; }; 3DFEBFA01EFBFC2300317D5C /* taxi_engine_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = taxi_engine_tests.cpp; sourceTree = ""; }; 3DFEBFA11EFBFC2300317D5C /* yandex_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = yandex_tests.cpp; sourceTree = ""; }; + BB1956E41F543D7B003ECE6C /* locals_api.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = locals_api.cpp; sourceTree = ""; }; + BB1956E51F543D7C003ECE6C /* locals_api.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = locals_api.hpp; sourceTree = ""; }; F67E75231DB8F06F00D6741F /* opentable_api.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = opentable_api.cpp; sourceTree = ""; }; F67E75241DB8F06F00D6741F /* opentable_api.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = opentable_api.hpp; sourceTree = ""; }; F6B536341DA5209F0067EEA5 /* libpartners_api.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libpartners_api.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -178,6 +182,8 @@ F6B5363B1DA520B20067EEA5 /* partners_api */ = { isa = PBXGroup; children = ( + BB1956E41F543D7B003ECE6C /* locals_api.cpp */, + BB1956E51F543D7C003ECE6C /* locals_api.hpp */, 3D47B2B01F14FA14000828D2 /* utils.hpp */, 3D47B2AA1F14BE89000828D2 /* cian_api.cpp */, 3D47B2AB1F14BE89000828D2 /* cian_api.hpp */, @@ -288,6 +294,7 @@ 3DFEBF9C1EFBFC1500317D5C /* taxi_engine.hpp in Headers */, F6B536431DA520E40067EEA5 /* uber_api.hpp in Headers */, 3DBC1C551E4B14920016897F /* facebook_ads.hpp in Headers */, + BB1956E71F543D7C003ECE6C /* locals_api.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,6 +405,7 @@ 346E88981E9D087400D4CE9B /* ads_engine.cpp in Sources */, 3D47B29B1F054C89000828D2 /* taxi_countries.cpp in Sources */, F67E75251DB8F06F00D6741F /* opentable_api.cpp in Sources */, + BB1956E61F543D7C003ECE6C /* locals_api.cpp in Sources */, 3DFEBFA31EFBFC2300317D5C /* taxi_engine_tests.cpp in Sources */, F6B536401DA520E40067EEA5 /* booking_api.cpp in Sources */, 3D47B29A1F054C89000828D2 /* taxi_base.cpp in Sources */,