diff --git a/configure.sh b/configure.sh index 659c24725d..cf3d5dbe69 100755 --- a/configure.sh +++ b/configure.sh @@ -89,6 +89,7 @@ else #define LOCALS_PAGE_URL "" #define PASSPORT_URL "" #define PASSPORT_APP_NAME "" +#define UGC_URL "" ' > "$PRIVATE_HEADER" echo 'ext { diff --git a/map/user.cpp b/map/user.cpp index 6007bfcb8b..474852c56f 100644 --- a/map/user.cpp +++ b/map/user.cpp @@ -11,21 +11,23 @@ #include #include -#include #define STAGE_PASSPORT_SERVER +#define STAGE_UGC_SERVER #include "private.h" namespace { std::string const kMapsMeTokenKey = "MapsMeToken"; -std::string const kServerUrl = PASSPORT_URL; +std::string const kReviewIdsKey = "UserReviewIds"; +std::string const kPassportServerUrl = PASSPORT_URL; std::string const kAppName = PASSPORT_APP_NAME; +std::string const kUGCServerUrl = UGC_URL; std::string AuthenticationUrl(std::string const & socialToken, User::SocialTokenType socialTokenType) { - if (kServerUrl.empty()) + if (kPassportServerUrl.empty()) return {}; std::string socialTokenStr; @@ -43,11 +45,18 @@ std::string AuthenticationUrl(std::string const & socialToken, } std::ostringstream ss; - ss << kServerUrl << "/register-by-token/" << socialTokenStr + ss << kPassportServerUrl << "/register-by-token/" << socialTokenStr << "/?access_token=" << UrlEncode(socialToken) << "&app=" << kAppName; return ss.str(); } +std::string UserDetailsUrl() +{ + std::ostringstream ss; + ss << kUGCServerUrl << "/user/reviews"; + return ss.str(); +} + std::string ParseAccessToken(std::string const & src) { my::Json root(src.c_str()); @@ -55,6 +64,30 @@ std::string ParseAccessToken(std::string const & src) FromJSONObject(root.get(), "access_token", tokenStr); return tokenStr; } + +std::vector DeserializeReviewIds(std::string const & reviewIdsSrc) +{ + my::Json root(reviewIdsSrc.c_str()); + if (json_array_size(root.get()) == 0) + return {}; + + std::vector result; + try + { + result.resize(json_array_size(root.get())); + for (size_t i = 0; i < result.size(); ++i) + { + auto const item = json_array_get(root.get(), i); + FromJSON(item, result[i]); + } + } + catch(my::Json::Exception const & ex) + { + LOG(LWARNING, ("Review ids deserialization failed.")); + return {}; + } + return result; +} } // namespace User::User() @@ -71,12 +104,20 @@ User::~User() void User::Init() { + std::lock_guard lock(m_mutex); + std::string token; if (GetPlatform().GetSecureStorage().Load(kMapsMeTokenKey, token)) - { - std::lock_guard lock(m_mutex); m_accessToken = token; - } + + std::string reviewIds; + if (GetPlatform().GetSecureStorage().Load(kReviewIdsKey, reviewIds)) + m_details.m_reviewIds = DeserializeReviewIds(reviewIds); + + // Update user details on start up. + auto const status = GetPlatform().ConnectionStatus(); + if (!m_accessToken.empty() && status == Platform::EConnectionType::CONNECTION_WIFI) + RequestUserDetails(); } void User::ResetAccessToken() @@ -86,23 +127,39 @@ void User::ResetAccessToken() GetPlatform().GetSecureStorage().Remove(kMapsMeTokenKey); } +void User::UpdateUserDetails() +{ + std::lock_guard lock(m_mutex); + if (m_authenticationInProgress || m_accessToken.empty()) + return; + + RequestUserDetails(); +} + bool User::IsAuthenticated() const { std::lock_guard lock(m_mutex); return !m_accessToken.empty(); } -std::string const & User::GetAccessToken() const +std::string User::GetAccessToken() const { std::lock_guard lock(m_mutex); return m_accessToken; } +User::Details User::GetDetails() const +{ + std::lock_guard lock(m_mutex); + return m_details; +} + void User::SetAccessToken(std::string const & accessToken) { std::lock_guard lock(m_mutex); m_accessToken = accessToken; GetPlatform().GetSecureStorage().Save(kMapsMeTokenKey, m_accessToken); + RequestUserDetails(); } void User::Authenticate(std::string const & socialToken, SocialTokenType socialTokenType) @@ -122,45 +179,87 @@ void User::Authenticate(std::string const & socialToken, SocialTokenType socialT } //TODO: refactor this after adding support of delayed tasks in WorkerThread. + // Also we need to strictly control destructors order to eliminate the case when + // a delayed task calls destructed object. m_workerThread.Push([this, url]() { - uint8_t constexpr kAttemptsCount = 3; - uint32_t constexpr kWaitingInSeconds = 5; - uint32_t constexpr kDegradationScalar = 2; - - uint32_t waitingTime = kWaitingInSeconds; - for (uint8_t i = 0; i < kAttemptsCount; ++i) + Request(url, {}, [this](std::string const & response) { - platform::HttpClient request(url); - request.SetRawHeader("Accept", "application/json"); - // TODO: Now passport service uses redirection. If it becomes false, uncomment checking. - if (request.RunHttpRequest())// && !request.WasRedirected()) - { - if (request.ErrorCode() == 200) // Ok. - { - SetAccessToken(ParseAccessToken(request.ServerResponse())); - break; - } - - if (request.ErrorCode() == 403) // Forbidden. - { - ResetAccessToken(); - break; - } - } - - // Wait for some time and retry. - std::unique_lock lock(m_mutex); - m_condition.wait_for(lock, std::chrono::seconds(waitingTime), - [this]{return m_needTerminate;}); - if (m_needTerminate) - break; - waitingTime *= kDegradationScalar; - } - - { - std::lock_guard lock(m_mutex); - m_authenticationInProgress = false; - } + SetAccessToken(ParseAccessToken(response)); + }); + std::lock_guard lock(m_mutex); + m_authenticationInProgress = false; }); } + +void User::RequestUserDetails() +{ + std::string const url = UserDetailsUrl(); + if (url.empty()) + { + LOG(LWARNING, ("User details service is unavailable.")); + return; + } + + if (m_accessToken.empty()) + return; + + m_workerThread.Push([this, url]() + { + auto const headers = std::map{{"Authorization", m_accessToken}}; + Request(url, headers, [this](std::string const & response) + { + auto const reviewIds = DeserializeReviewIds(response); + if (!reviewIds.empty()) + { + GetPlatform().GetSecureStorage().Save(kReviewIdsKey, response); + std::lock_guard lock(m_mutex); + m_details.m_reviewIds = reviewIds; + } + }); + }); +} + +void User::Request(std::string const & url, + std::map const & headers, + std::function const & onSuccess) +{ + ASSERT(onSuccess != nullptr, ()); + + uint8_t constexpr kAttemptsCount = 3; + uint32_t constexpr kWaitingInSeconds = 5; + uint32_t constexpr kDegradationScalar = 2; + + uint32_t waitingTime = kWaitingInSeconds; + for (uint8_t i = 0; i < kAttemptsCount; ++i) + { + platform::HttpClient request(url); + request.SetRawHeader("Accept", "application/json"); + for (auto const & header : headers) + request.SetRawHeader(header.first, header.second); + + // TODO: Now passport service uses redirection. If it becomes false, uncomment checking. + if (request.RunHttpRequest())// && !request.WasRedirected()) + { + if (request.ErrorCode() == 200) // Ok. + { + onSuccess(request.ServerResponse()); + break; + } + + if (request.ErrorCode() == 403) // Forbidden. + { + ResetAccessToken(); + break; + } + } + + // Wait for some time and retry. + std::unique_lock lock(m_mutex); + m_condition.wait_for(lock, std::chrono::seconds(waitingTime), + [this]{return m_needTerminate;}); + if (m_needTerminate) + break; + waitingTime *= kDegradationScalar; + } +} diff --git a/map/user.hpp b/map/user.hpp index da8b95043e..2a1808f4c0 100644 --- a/map/user.hpp +++ b/map/user.hpp @@ -3,13 +3,21 @@ #include "base/worker_thread.hpp" #include +#include +#include #include #include +#include // This class is thread-safe. class User { public: + struct Details + { + using ReviewId = uint64_t; + std::vector m_reviewIds; + }; enum SocialTokenType { Facebook, @@ -21,17 +29,24 @@ public: void Authenticate(std::string const & socialToken, SocialTokenType socialTokenType); bool IsAuthenticated() const; void ResetAccessToken(); + void UpdateUserDetails(); - std::string const & GetAccessToken() const; + std::string GetAccessToken() const; + Details GetDetails() const; private: void Init(); void SetAccessToken(std::string const & accessToken); + void RequestUserDetails(); + void Request(std::string const & url, + std::map const & headers, + std::function const & onSuccess); std::string m_accessToken; mutable std::mutex m_mutex; std::condition_variable m_condition; bool m_needTerminate = false; bool m_authenticationInProgress = false; + Details m_details; base::WorkerThread m_workerThread; };