diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index 9b867b6c9b..cfd27d41fb 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -21,6 +21,8 @@ set( booking_filter_params.hpp booking_filter_processor.cpp booking_filter_processor.hpp + booking_utils.cpp + booking_utils.hpp bookmark.cpp bookmark.hpp bookmark_catalog.cpp diff --git a/map/booking_availability_filter.cpp b/map/booking_availability_filter.cpp index 02429f7782..1ecb5d13fa 100644 --- a/map/booking_availability_filter.cpp +++ b/map/booking_availability_filter.cpp @@ -30,24 +30,26 @@ class HotelInfo { public: explicit HotelInfo(T const & requestedVia) : m_mappedValue(requestedVia) {} - HotelInfo(std::string const & hotelId, availability::Cache::HotelStatus const & cacheStatus, - T const & mappedValue) - : m_hotelId(hotelId), m_cacheStatus(cacheStatus), m_mappedValue(mappedValue) + HotelInfo(std::string const & hotelId, availability::Cache::Info && info, T const & mappedValue) + : m_hotelId(hotelId), m_info(std::move(info)), m_mappedValue(mappedValue) { } void SetHotelId(std::string const & hotelId) { m_hotelId = hotelId; } - void SetStatus(availability::Cache::HotelStatus cacheStatus) { m_cacheStatus = cacheStatus; } + void SetInfo(availability::Cache::Info && info) { m_info = std::move(info); } std::string const & GetHotelId() const { return m_hotelId; } - availability::Cache::HotelStatus GetStatus() const { return m_cacheStatus; } + availability::Cache::HotelStatus GetStatus() const { return m_info.m_status; } + + std::optional const & GetExtras() const { return m_info.m_extras; } + std::optional & GetExtras() { return m_info.m_extras; } T const & GetMappedValue() const { return m_mappedValue; } T & GetMappedValue() { return m_mappedValue; } private: std::string m_hotelId; - availability::Cache::HotelStatus m_cacheStatus = availability::Cache::HotelStatus::Absent; + availability::Cache::Info m_info; T m_mappedValue; }; @@ -60,33 +62,29 @@ using HotelToResults = HotelsMapping; using HotelToFeatureIds = HotelsMapping; template -void UpdateCache(HotelsMapping const & hotelsMapping, std::vector const & hotelIds, +void UpdateCache(HotelsMapping const & hotelsMapping, booking::HotelsWithExtras const & hotels, availability::Cache & cache) { using availability::Cache; - ASSERT(std::is_sorted(hotelIds.begin(), hotelIds.end()), ()); - for (auto & hotel : hotelsMapping) { if (hotel.GetStatus() != Cache::HotelStatus::Absent) continue; - - if (std::binary_search(hotelIds.cbegin(), hotelIds.cend(), hotel.GetHotelId())) - cache.Insert(hotel.GetHotelId(), Cache::HotelStatus::Available); + auto const it = hotels.find(hotel.GetHotelId()); + if (it != hotels.cend()) + cache.InsertAvailable(hotel.GetHotelId(), {it->second.m_price, it->second.m_currency}); else - cache.Insert(hotel.GetHotelId(), Cache::HotelStatus::Unavailable); + cache.InsertUnavailable(hotel.GetHotelId()); } } template -void FillResults(HotelsMapping && hotelsMapping, std::vector const & hotelIds, +void FillResults(HotelsMapping && hotelsMapping, booking::HotelsWithExtras && hotels, availability::Cache & cache, Inserter const & inserter) { using availability::Cache; - ASSERT(std::is_sorted(hotelIds.begin(), hotelIds.end()), ()); - for (auto & hotel : hotelsMapping) { switch (hotel.GetStatus()) @@ -94,22 +92,23 @@ void FillResults(HotelsMapping && hotelsMapping, std::vector con case Cache::HotelStatus::Unavailable: continue; case Cache::HotelStatus::Available: { - inserter(std::move(hotel.GetMappedValue())); + inserter(std::move(hotel.GetMappedValue()), std::move(*hotel.GetExtras())); continue; } case Cache::HotelStatus::NotReady: { - auto hotelStatus = cache.Get(hotel.GetHotelId()); + auto info = cache.Get(hotel.GetHotelId()); - if (hotelStatus == Cache::HotelStatus::Available) - inserter(std::move(hotel.GetMappedValue())); + if (info.m_status == Cache::HotelStatus::Available) + inserter(std::move(hotel.GetMappedValue()), std::move(*hotel.GetExtras())); continue; } case Cache::HotelStatus::Absent: { - if (std::binary_search(hotelIds.cbegin(), hotelIds.cend(), hotel.GetHotelId())) - inserter(std::move(hotel.GetMappedValue())); + auto const it = hotels.find(hotel.GetHotelId()); + if (it != hotels.cend()) + inserter(std::move(hotel.GetMappedValue()), std::move(it->second)); continue; } @@ -117,26 +116,28 @@ void FillResults(HotelsMapping && hotelsMapping, std::vector con } } -void FillResults(HotelToResults && hotelToResults, std::vector const & hotelIds, - availability::Cache & cache, search::Results & results) +void FillResults(HotelToResults && hotelToResults, booking::HotelsWithExtras && hotels, + availability::Cache & cache, search::Results & results, std::vector & extras) { - auto const inserter = [&results](search::Result && result) + auto const inserter = [&results, &extras](search::Result && result, booking::Extras && extra) { results.AddResult(std::move(result)); + extras.emplace_back(std::move(extra)); }; - FillResults(std::move(hotelToResults), hotelIds, cache, inserter); + FillResults(std::move(hotelToResults), std::move(hotels), cache, inserter); } -void FillResults(HotelToFeatureIds && hotelToFeatureIds, std::vector const & hotelIds, - availability::Cache & cache, std::vector & results) +void FillResults(HotelToFeatureIds && hotelToFeatureIds, booking::HotelsWithExtras && hotels, + availability::Cache & cache, std::vector & results, std::vector & extras) { - auto const inserter = [&results](FeatureID && result) + auto const inserter = [&results, &extras](FeatureID && result, booking::Extras && extra) { results.emplace_back(std::move(result)); + extras.emplace_back(std::move(extra)); }; - FillResults(std::move(hotelToFeatureIds), hotelIds, cache, inserter); + FillResults(std::move(hotelToFeatureIds), std::move(hotels), cache, inserter); } void PrepareData(DataSource const & dataSource, search::Results const & results, @@ -184,16 +185,17 @@ void PrepareData(DataSource const & dataSource, search::Results const & results, if (!ftypes::IsBookingChecker::Instance()(*ft)) continue; - auto const hotelId = ft->GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); - auto const status = cache.Get(hotelId); + auto hotelId = ft->GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); + auto info = cache.Get(hotelId); + auto const status = info.m_status; it->SetHotelId(hotelId); - it->SetStatus(status); + it->SetInfo(std::move(info)); if (status != availability::Cache::HotelStatus::Absent) continue; - cache.Reserve(hotelId); + cache.InsertNotReady(hotelId); p.m_hotelIds.push_back(std::move(hotelId)); } } @@ -225,14 +227,15 @@ void PrepareData(DataSource const & dataSource, std::vector const & f continue; auto const hotelId = ft->GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); - auto const status = cache.Get(hotelId); + auto info = cache.Get(hotelId); + auto const status = info.m_status; - hotelToFeatures.emplace_back(hotelId, status, featureId); + hotelToFeatures.emplace_back(hotelId, std::move(info), featureId); if (status != availability::Cache::HotelStatus::Absent) continue; - cache.Reserve(hotelId); + cache.InsertNotReady(hotelId); p.m_hotelIds.push_back(std::move(hotelId)); } } @@ -285,25 +288,26 @@ void AvailabilityFilter::ApplyFilterInternal(Source const & source, Parameters c if (m_apiParams.m_hotelIds.empty()) { Source result; - FillResults(std::move(hotelsToSourceValue), {} /* hotelIds */, *m_cache, result); - cb(result); + std::vector extras; + FillResults(std::move(hotelsToSourceValue), {} /* hotelIds */, *m_cache, result, extras); + cb(std::move(result), std::move(extras)); return; } auto const apiCallback = - [cb, cache = m_cache, hotelToValue = std::move(hotelsToSourceValue)] - (std::vector hotelIds) mutable + [cb, cache = m_cache, hotelToValue = std::move(hotelsToSourceValue)] (booking::HotelsWithExtras hotels) mutable { GetPlatform().RunTask( Platform::Thread::File, - [cb, cache, hotelToValue = std::move(hotelToValue), hotelIds = std::move(hotelIds)]() mutable + [cb, cache, hotelToValue = std::move(hotelToValue), hotels = std::move(hotels)]() mutable { + UpdateCache(hotelToValue, hotels, *cache); + Source updatedResults; - std::sort(hotelIds.begin(), hotelIds.end()); - UpdateCache(hotelToValue, hotelIds, *cache); - FillResults(std::move(hotelToValue), hotelIds, *cache, updatedResults); - cb(updatedResults); + std::vector extras; + FillResults(std::move(hotelToValue), std::move(hotels), *cache, updatedResults, extras); + cb(std::move(updatedResults), std::move(extras)); }); }; @@ -312,7 +316,8 @@ void AvailabilityFilter::ApplyFilterInternal(Source const & source, Parameters c } void AvailabilityFilter::GetFeaturesFromCache(search::Results const & results, - std::vector & sortedResults) + std::vector & sortedResults, + std::vector & extras) { sortedResults.clear(); @@ -347,8 +352,12 @@ void AvailabilityFilter::GetFeaturesFromCache(search::Results const & results, auto const & hotelId = ft->GetMetadata().Get(feature::Metadata::FMD_SPONSORED_ID); - if (m_cache->Get(hotelId) == availability::Cache::HotelStatus::Available) + auto info = m_cache->Get(hotelId); + if (info.m_status == availability::Cache::HotelStatus::Available) + { sortedResults.push_back(featureId); + extras.emplace_back(std::move(*info.m_extras)); + } } } } // namespace booking diff --git a/map/booking_availability_filter.hpp b/map/booking_availability_filter.hpp index 5b6c493c77..0cc7450133 100644 --- a/map/booking_availability_filter.hpp +++ b/map/booking_availability_filter.hpp @@ -25,8 +25,8 @@ public: void ApplyFilter(std::vector const & featureIds, ParamsRawInternal const & params) override; - void GetFeaturesFromCache(search::Results const & results, - std::vector & sortedResults) override; + void GetFeaturesFromCache(search::Results const & results, std::vector & sortedResults, + std::vector & extras) override; void UpdateParams(ParamsBase const & apiParams) override; private: diff --git a/map/booking_filter.hpp b/map/booking_filter.hpp index d6f30e2e80..84c7b03bd4 100644 --- a/map/booking_filter.hpp +++ b/map/booking_filter.hpp @@ -40,7 +40,8 @@ public: virtual void ApplyFilter(std::vector const & featureIds, ParamsRawInternal const & params) = 0; virtual void GetFeaturesFromCache(search::Results const & results, - std::vector & sortedResults) = 0; + std::vector & sortedResults, + std::vector & extras) = 0; virtual void UpdateParams(ParamsBase const & apiParams) = 0; Delegate const & GetDelegate() const { return m_delegate; } diff --git a/map/booking_filter_cache.cpp b/map/booking_filter_cache.cpp index ad6719ac7d..31a9fb0236 100644 --- a/map/booking_filter_cache.cpp +++ b/map/booking_filter_cache.cpp @@ -8,38 +8,99 @@ namespace filter { namespace availability { +namespace +{ +bool IsExpired(Cache::Clock::time_point const & timestamp, size_t expiryPeriod) +{ + return Cache::Clock::now() > timestamp + seconds(expiryPeriod); +} + +template +bool IsExpired(Item const & item, size_t expiryPeriod) +{ + return Cache::Clock::now() > item.m_timestamp + seconds(expiryPeriod); +} + +template +typename MapType::const_iterator GetOrRemove(MapType & src, std::string const & hotelId, + size_t expiryPeriod) +{ + auto const it = src.find(hotelId); + + if (it == src.cend()) + return src.cend(); + + if (expiryPeriod != 0 && IsExpired(it->second, expiryPeriod)) + { + src.erase(it); + return src.cend(); + } + + return it; +} + +template +void Remove(MapType & src, Pred const & pred) +{ + for (auto it = src.begin(); it != src.end();) + { + if (pred(it->second)) + it = src.erase(it); + else + ++it; + } +} +} // namespace + Cache::Cache(size_t maxCount, size_t expiryPeriodSeconds) : m_maxCount(maxCount), m_expiryPeriodSeconds(expiryPeriodSeconds) { } -Cache::HotelStatus Cache::Get(std::string const & hotelId) +Cache::Info Cache::Get(std::string const & hotelId) { - HotelStatus result = Get(m_notReadyHotels, hotelId); + auto const notReadyIt = GetOrRemove(m_notReadyHotels, hotelId, m_expiryPeriodSeconds); - if (result == HotelStatus::Absent) - result = Get(m_hotelToStatus, hotelId); + if (notReadyIt != m_notReadyHotels.cend()) + return Info(HotelStatus::NotReady); - return result; + auto const unavailableIt = GetOrRemove(m_unavailableHotels, hotelId, m_expiryPeriodSeconds); + + if (unavailableIt != m_unavailableHotels.cend()) + return Info(HotelStatus::Unavailable); + + auto const availableIt = GetOrRemove(m_availableHotels, hotelId, m_expiryPeriodSeconds); + + if (availableIt != m_availableHotels.cend()) + return Info(HotelStatus::Available, availableIt->second.m_extras); + + return Info(HotelStatus::Absent); } -void Cache::Reserve(std::string const & hotelId) +void Cache::InsertNotReady(std::string const & hotelId) { - ASSERT(m_hotelToStatus.find(hotelId) == m_hotelToStatus.end(), ()); + ASSERT(m_unavailableHotels.find(hotelId) == m_unavailableHotels.end(), ()); + ASSERT(m_availableHotels.find(hotelId) == m_availableHotels.end(), ()); - m_notReadyHotels.emplace(hotelId, Item()); + m_notReadyHotels.emplace(hotelId, Clock::now()); } -void Cache::Insert(std::string const & hotelId, HotelStatus const s) +void Cache::InsertUnavailable(std::string const & hotelId) { - ASSERT_NOT_EQUAL(s, HotelStatus::NotReady, - ("Please, use Cache::Reserve method for HotelStatus::NotReady")); + RemoveOverly(); - RemoveExtra(); - - Item item(s); - m_hotelToStatus[hotelId] = std::move(item); + m_unavailableHotels[hotelId] = Clock::now(); m_notReadyHotels.erase(hotelId); + m_availableHotels.erase(hotelId); +} + +void Cache::InsertAvailable(std::string const & hotelId, Extras && extras) +{ + RemoveOverly(); + + m_availableHotels[hotelId] = Item(std::move(extras)); + m_notReadyHotels.erase(hotelId); + m_unavailableHotels.erase(hotelId); } void Cache::RemoveOutdated() @@ -47,54 +108,27 @@ void Cache::RemoveOutdated() if (m_expiryPeriodSeconds == 0) return; - RemoveOutdated(m_hotelToStatus); - RemoveOutdated(m_notReadyHotels); + Remove(m_notReadyHotels, [this](auto const & v) { return IsExpired(v, m_expiryPeriodSeconds); }); + Remove(m_unavailableHotels, + [this](auto const & v) { return IsExpired(v, m_expiryPeriodSeconds); }); + Remove(m_availableHotels, + [this](auto const & v) { return IsExpired(v.m_timestamp, m_expiryPeriodSeconds); }); } void Cache::Clear() { - m_hotelToStatus.clear(); m_notReadyHotels.clear(); + m_unavailableHotels.clear(); + m_availableHotels.clear(); } -void Cache::RemoveExtra() +void Cache::RemoveOverly() { - if (m_maxCount == 0 || m_hotelToStatus.size() < m_maxCount) - return; + if (m_maxCount != 0 && m_unavailableHotels.size() >= m_maxCount) + m_unavailableHotels.clear(); - m_hotelToStatus.clear(); -} - -bool Cache::IsExpired(Clock::time_point const & timestamp) const -{ - return Clock::now() > timestamp + seconds(m_expiryPeriodSeconds); -} - -Cache::HotelStatus Cache::Get(HotelsMap & src, std::string const & hotelId) -{ - auto const it = src.find(hotelId); - - if (it == src.cend()) - return HotelStatus::Absent; - - if (m_expiryPeriodSeconds != 0 && IsExpired(it->second.m_timestamp)) - { - src.erase(it); - return HotelStatus::Absent; - } - - return it->second.m_status; -} - -void Cache::RemoveOutdated(HotelsMap & src) -{ - for (auto it = src.begin(); it != src.end();) - { - if (IsExpired(it->second.m_timestamp)) - it = src.erase(it); - else - ++it; - } + if (m_maxCount != 0 && m_availableHotels.size() >= m_maxCount) + m_availableHotels.clear(); } std::string DebugPrint(Cache::HotelStatus status) diff --git a/map/booking_filter_cache.hpp b/map/booking_filter_cache.hpp index 78064df4fe..fbb3eace2f 100644 --- a/map/booking_filter_cache.hpp +++ b/map/booking_filter_cache.hpp @@ -1,9 +1,13 @@ #pragma once +#include "partners_api/booking_api.hpp" + #include "base/macros.hpp" #include #include +#include +#include namespace booking { @@ -28,40 +32,49 @@ public: using Clock = std::chrono::steady_clock; - struct Item + struct Info { - Item() = default; + Info() = default; + explicit Info(HotelStatus status) : m_status(status) {} + Info(HotelStatus status, Extras const & extras) : m_status(status), m_extras(extras) {} - explicit Item(HotelStatus s) : m_status(s) {} - - Clock::time_point m_timestamp = Clock::now(); - HotelStatus m_status = HotelStatus::NotReady; + HotelStatus m_status = HotelStatus::Absent; + std::optional m_extras; }; Cache() = default; Cache(size_t maxCount, size_t expiryPeriodSeconds); - HotelStatus Get(std::string const & hotelId); - void Reserve(std::string const & hotelId); - void Insert(std::string const & hotelId, HotelStatus const s); + Info Get(std::string const & hotelId); + void InsertNotReady(std::string const & hotelId); + void InsertUnavailable(std::string const & hotelId); + void InsertAvailable(std::string const & hotelId, Extras && extras); void RemoveOutdated(); void Clear(); private: - using HotelsMap = std::map; + struct Item + { + Item() = default; + explicit Item(Extras && extras) : m_extras(std::move(extras)) {} + + Clock::time_point m_timestamp = Clock::now(); + Extras m_extras; + }; + + using HotelWithTimestampMap = std::map; + using HotelWithExtrasMap = std::map; // In case when size >= |m_maxCount| removes items except those who have the status // HotelStatus::NotReady. - void RemoveExtra(); - bool IsExpired(Clock::time_point const & timestamp) const; - HotelStatus Get(HotelsMap & src, std::string const & hotelId); - void RemoveOutdated(HotelsMap & src); + void RemoveOverly(); - HotelsMap m_hotelToStatus; - HotelsMap m_notReadyHotels; - // Max count of |m_hotelToStatus| container. + HotelWithTimestampMap m_notReadyHotels; + HotelWithTimestampMap m_unavailableHotels; + HotelWithExtrasMap m_availableHotels; + // Max count of |m_availableHotels| or |m_unavailableHotels| container. // Count is unlimited when |m_maxCount| is equal to zero. - size_t const m_maxCount = 5000; + size_t const m_maxCount = 3000; // Do not use aging when |m_expiryPeriodSeconds| is equal to zero. size_t const m_expiryPeriodSeconds = 300; }; diff --git a/map/booking_filter_params.hpp b/map/booking_filter_params.hpp index c1e721b416..a6646a9c58 100644 --- a/map/booking_filter_params.hpp +++ b/map/booking_filter_params.hpp @@ -1,12 +1,13 @@ #pragma once +#include "partners_api/booking_api.hpp" #include "partners_api/booking_availability_params.hpp" #include "partners_api/booking_params_base.hpp" -#include "platform/safe_callback.hpp" - #include "indexer/feature_decl.hpp" +#include "platform/safe_callback.hpp" + #include #include #include @@ -24,8 +25,10 @@ namespace filter { using Results = platform::SafeCallback const & params, std::vector const & sortedFeatures)>; -using ResultsUnsafe = std::function; -using ResultsRawUnsafe = std::function const & results)>; +using ResultsUnsafe = + std::function && extras)>; +using ResultsRawUnsafe = + std::function && sortedFeatures, std::vector && extras)>; template struct ParamsImpl diff --git a/map/booking_filter_processor.cpp b/map/booking_filter_processor.cpp index eb1df1e4c4..255c9ebf74 100644 --- a/map/booking_filter_processor.cpp +++ b/map/booking_filter_processor.cpp @@ -48,11 +48,10 @@ void FilterProcessor::GetFeaturesFromCache(Types const & types, search::Results for (auto const type : types) { std::vector featuresSorted; - m_filters.at(type)->GetFeaturesFromCache(results, featuresSorted); + std::vector extras; + m_filters.at(type)->GetFeaturesFromCache(results, featuresSorted, extras); - ASSERT(std::is_sorted(featuresSorted.begin(), featuresSorted.end()), ()); - - cachedResults.emplace_back(type, std::move(featuresSorted)); + cachedResults.emplace_back(type, std::move(featuresSorted), std::move(extras)); } callback(std::move(cachedResults)); @@ -92,9 +91,9 @@ void FilterProcessor::ApplyConsecutively(Source const & source, TaskInternalType auto const & cb = tasks[i - 1].m_filterParams.m_callback; tasks[i - 1].m_filterParams.m_callback = - [ this, cb, nextTask = std::move(tasks[i]) ](auto const & filterResults) mutable + [ this, cb, nextTask = std::move(tasks[i]) ](auto && filterResults, auto && extras) mutable { - cb(filterResults); + cb(std::move(filterResults), std::move(extras)); // Run the next filter with obtained results from the previous one. // Post different task on the file thread to increase granularity. // Note: FilterProcessor works on file thread, so all filters will diff --git a/map/booking_filter_processor.hpp b/map/booking_filter_processor.hpp index 4ece328e64..285bcb91f1 100644 --- a/map/booking_filter_processor.hpp +++ b/map/booking_filter_processor.hpp @@ -10,6 +10,7 @@ #include "base/macros.hpp" #include +#include #include class DataSource; @@ -28,14 +29,16 @@ namespace filter struct CachedResult { - CachedResult(Type type, std::vector && featuresSorted) + CachedResult(Type type, std::vector && featuresSorted, std::vector && extras) : m_type(type) - , m_featuresSorted(featuresSorted) + , m_featuresSorted(std::move(featuresSorted)) + , m_extras(std::move(extras)) { } Type m_type; std::vector m_featuresSorted; + std::vector m_extras; }; using CachedResults = std::vector; diff --git a/map/booking_utils.cpp b/map/booking_utils.cpp new file mode 100644 index 0000000000..a3a420b951 --- /dev/null +++ b/map/booking_utils.cpp @@ -0,0 +1,155 @@ +#include "map/booking_utils.hpp" + +#include "map/search_mark.hpp" + +#include "indexer/feature_decl.hpp" + +#include + +namespace booking +{ +namespace +{ +void Sort(search::Results && results, std::vector && extras, + std::vector & sortedFeatures, std::vector & sortedExtras) +{ + if (!extras.empty()) + { + std::vector> featuresWithExtras; + featuresWithExtras.reserve(results.GetCount()); + for (size_t i = 0; i < results.GetCount(); ++i) + featuresWithExtras.emplace_back(std::move(results[i].GetFeatureID()), std::move(extras[i])); + + std::sort(featuresWithExtras.begin(), featuresWithExtras.end(), + [](auto const & lhs, auto const & rhs) { return lhs.first < rhs.first; }); + + sortedFeatures.reserve(featuresWithExtras.size()); + sortedExtras.reserve(featuresWithExtras.size()); + for (auto & item : featuresWithExtras) + { + sortedFeatures.emplace_back(std::move(item.first)); + sortedExtras.emplace_back(std::move(item.second)); + } + } + else + { + for (auto const & r : results) + sortedFeatures.push_back(r.GetFeatureID()); + + std::sort(sortedFeatures.begin(), sortedFeatures.end()); + } +} +} // namespace + +filter::TasksInternal MakeInternalTasks(filter::Tasks const & filterTasks, + SearchMarks & searchMarks, bool inViewport) +{ + using namespace booking::filter; + + TasksInternal tasksInternal; + + for (auto const & task : filterTasks) + { + auto const type = task.m_type; + auto const & apiParams = task.m_filterParams.m_apiParams; + auto const & cb = task.m_filterParams.m_callback; + + if (apiParams->IsEmpty()) + continue; + + ParamsInternal paramsInternal + { + apiParams, + [&searchMarks, type, apiParams, cb, inViewport](auto && results, auto && extras) + { + if (results.GetCount() == 0) + return; + + std::vector sortedFeatures; + std::vector sortedExtras; + Sort(std::move(results), std::move(extras), sortedFeatures, sortedExtras); + + if (inViewport) + { + // TODO(a): add price formatting for array. + // auto const pricesFormatted = FormatPrices(sortedExtras); + + GetPlatform().RunTask(Platform::Thread::Gui, [&searchMarks, type, sortedFeatures]() + { + switch (type) + { + case Type::Deals: + searchMarks.SetSales(sortedFeatures, true /* hasSale */); + break; + case Type::Availability: + searchMarks.SetPreparingState(sortedFeatures, false /* isPreparing */); + break; + } + }); + + // TODO(a): to add SetPrices method into search marks. + // if (!pricesFormatted.empty()) + // m_searchMarks.SetPrices(sortedFeatures, pricesFormatted) + } + cb(apiParams, sortedFeatures); + } + }; + + tasksInternal.emplace_back(type, move(paramsInternal)); + } + + return tasksInternal; +} + +filter::TasksRawInternal MakeInternalTasks(filter::Tasks const & filterTasks, + SearchMarks & searchMarks) +{ + using namespace booking::filter; + + TasksRawInternal tasksInternal; + + for (auto const & task : filterTasks) + { + auto const type = task.m_type; + auto const & apiParams = task.m_filterParams.m_apiParams; + auto const & cb = task.m_filterParams.m_callback; + + if (apiParams->IsEmpty()) + continue; + + ParamsRawInternal paramsInternal + { + apiParams, + [&searchMarks, type, apiParams, cb](auto && sortedFeatures, auto && extras) + { + if (sortedFeatures.empty()) + return; + + // TODO(a): add price formatting for array. + // auto const pricesFormatted = FormatPrices(extras); + + GetPlatform().RunTask(Platform::Thread::Gui, [&searchMarks, type, sortedFeatures]() + { + switch (type) + { + case Type::Deals: + searchMarks.SetSales(sortedFeatures, true /* hasSale */); + break; + case Type::Availability: + searchMarks.SetPreparingState(sortedFeatures, false /* isPreparing */); + break; + } + // TODO(a): to add SetPrices method into search marks. + // if (!pricesFormatted.empty()) + // m_searchMarks.SetPrices(sortedFeatures, pricesFormatted) + }); + cb(apiParams, sortedFeatures); + } + }; + + tasksInternal.emplace_back(type, move(paramsInternal)); + } + + return tasksInternal; +} +} // namespace booking diff --git a/map/booking_utils.hpp b/map/booking_utils.hpp new file mode 100644 index 0000000000..a554971b61 --- /dev/null +++ b/map/booking_utils.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "map/booking_filter_params.hpp" + +#include "search/result.hpp" + +#include + +struct FeatureID; + +class SearchMarks; + +namespace booking +{ +filter::TasksInternal MakeInternalTasks(filter::Tasks const & filterTasks, + SearchMarks & searchMarks, bool inViewport); +filter::TasksRawInternal MakeInternalTasks(filter::Tasks const & filterTasks, + SearchMarks & searchMarks); +} // namespace booking diff --git a/map/framework.cpp b/map/framework.cpp index be89065834..6863b2f389 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1,5 +1,6 @@ #include "map/framework.hpp" #include "map/benchmark_tools.hpp" +#include "map/booking_utils.hpp" #include "map/catalog_headers_provider.hpp" #include "map/chart_generator.hpp" #include "map/displayed_categories_modifiers.hpp" @@ -3894,14 +3895,24 @@ void Framework::ShowViewportSearchResults(search::Results::ConstIter begin, for (auto const & filterResult : filtersResults) { - auto const found = std::binary_search(filterResult.m_featuresSorted.cbegin(), - filterResult.m_featuresSorted.cend(), id); + auto const & features = filterResult.m_featuresSorted; + auto const it = std::lower_bound(features.cbegin(), features.cend(), id); + auto const found = it != features.cend(); switch (filterResult.m_type) { case Type::Deals: mark.SetSale(found); break; case Type::Availability: mark.SetPreparing(!found); break; } + + if (found && !filterResult.m_extras.empty()) + { + // auto const index = std::distance(features.cbegin(), it); + // TODO(a): to implement FormatPrice and SetPrice methods. + // auto const price = FormatPrice(filterResult.m_extras[index].m_price, + // filterResult.m_extras[index].m_currency); + // mark.SetPrice(price); + } } }; @@ -4170,101 +4181,14 @@ ugc::Reviews Framework::FilterUGCReviews(ugc::Reviews const & reviews) const void Framework::FilterResultsForHotelsQuery(booking::filter::Tasks const & filterTasks, search::Results const & results, bool inViewport) { - using namespace booking::filter; - - TasksInternal tasksInternal; - - for (auto const & task : filterTasks) - { - auto const type = task.m_type; - auto const & apiParams = task.m_filterParams.m_apiParams; - auto const & cb = task.m_filterParams.m_callback; - - if (apiParams->IsEmpty()) - continue; - - ParamsInternal paramsInternal - { - apiParams, - [this, type, apiParams, cb, inViewport](search::Results const & results) - { - if (results.GetCount() == 0) - return; - - std::vector features; - for (auto const & r : results) - features.push_back(r.GetFeatureID()); - - std::sort(features.begin(), features.end()); - - if (inViewport) - { - GetPlatform().RunTask(Platform::Thread::Gui, [this, type, features]() - { - switch (type) - { - case Type::Deals: - m_searchMarks.SetSales(features, true /* hasSale */); - break; - case Type::Availability: - m_searchMarks.SetPreparingState(features, false /* isPreparing */); - break; - } - }); - } - cb(apiParams, features); - } - }; - - tasksInternal.emplace_back(type, move(paramsInternal)); - } - + auto tasksInternal = booking::MakeInternalTasks(filterTasks, m_searchMarks, inViewport); m_bookingFilterProcessor.ApplyFilters(results, move(tasksInternal), filterTasks.GetMode()); } void Framework::FilterHotels(booking::filter::Tasks const & filterTasks, vector && featureIds) { - using namespace booking::filter; - - TasksRawInternal tasksInternal; - - for (auto const & task : filterTasks) - { - auto const type = task.m_type; - auto const & apiParams = task.m_filterParams.m_apiParams; - auto const & cb = task.m_filterParams.m_callback; - - if (apiParams->IsEmpty()) - continue; - - ParamsRawInternal paramsInternal - { - apiParams, - [this, type, apiParams, cb](vector const & features) - { - if (features.empty()) - return; - - GetPlatform().RunTask(Platform::Thread::Gui, [this, type, features]() - { - switch (type) - { - case Type::Deals: - m_searchMarks.SetSales(features, true /* hasSale */); - break; - case Type::Availability: - m_searchMarks.SetPreparingState(features, false /* isPreparing */); - break; - } - }); - cb(apiParams, features); - } - }; - - tasksInternal.emplace_back(type, move(paramsInternal)); - } - + auto tasksInternal = booking::MakeInternalTasks(filterTasks, m_searchMarks); m_bookingFilterProcessor.ApplyFilters(move(featureIds), move(tasksInternal), filterTasks.GetMode()); } diff --git a/map/map_tests/booking_availability_cache_test.cpp b/map/map_tests/booking_availability_cache_test.cpp index 30dbd33b3b..ae339f8918 100644 --- a/map/map_tests/booking_availability_cache_test.cpp +++ b/map/map_tests/booking_availability_cache_test.cpp @@ -16,19 +16,28 @@ UNIT_TEST(AvailabilityCache_Smoke) std::string kHotelId = "0"; - TEST_EQUAL(cache.Get(kHotelId), Cache::HotelStatus::Absent, ()); + auto info = cache.Get(kHotelId); + TEST_EQUAL(info.m_status, Cache::HotelStatus::Absent, ()); + TEST(!info.m_extras, ()); - cache.Reserve(kHotelId); + cache.InsertNotReady(kHotelId); - TEST_EQUAL(cache.Get(kHotelId), Cache::HotelStatus::NotReady, ()); + info = cache.Get(kHotelId); + TEST_EQUAL(info.m_status, Cache::HotelStatus::NotReady, ()); + TEST(!info.m_extras, ()); - cache.Insert(kHotelId, Cache::HotelStatus::Available); + cache.InsertAvailable(kHotelId, {10.0, "Y"}); - TEST_EQUAL(cache.Get(kHotelId), Cache::HotelStatus::Available, ()); + info = cache.Get(kHotelId); + TEST_EQUAL(info.m_status, Cache::HotelStatus::Available, ()); + TEST(info.m_extras, ()); + TEST_EQUAL(info.m_extras->m_currency, "Y", ()); - cache.Insert(kHotelId, Cache::HotelStatus::Unavailable); + cache.InsertUnavailable(kHotelId); - TEST_EQUAL(cache.Get(kHotelId), Cache::HotelStatus::Unavailable, ()); + info = cache.Get(kHotelId); + TEST_EQUAL(info.m_status, Cache::HotelStatus::Unavailable, ()); + TEST(!info.m_extras, ()); } UNIT_TEST(AvailabilityCache_RemoveExtra) @@ -37,19 +46,19 @@ UNIT_TEST(AvailabilityCache_RemoveExtra) std::vector const kHotelIds = {"1", "2", "3"}; for (auto const & id : kHotelIds) - TEST_EQUAL(cache.Get(id), Cache::HotelStatus::Absent, ()); + TEST_EQUAL(cache.Get(id).m_status, Cache::HotelStatus::Absent, ()); for (auto const & id : kHotelIds) - cache.Insert(id, Cache::HotelStatus::Available); + cache.InsertAvailable(id, {1.0, "X"}); for (auto const & id : kHotelIds) - TEST_EQUAL(cache.Get(id), Cache::HotelStatus::Available, ()); + TEST_EQUAL(cache.Get(id).m_status, Cache::HotelStatus::Available, ()); - cache.Insert("4", Cache::HotelStatus::Available); + cache.InsertAvailable("4", {1.0, "X"}); for (auto const & id : kHotelIds) - TEST_EQUAL(cache.Get(id), Cache::HotelStatus::Absent, ()); + TEST_EQUAL(cache.Get(id).m_status, Cache::HotelStatus::Absent, ()); - TEST_EQUAL(cache.Get("4"), Cache::HotelStatus::Available, ()); + TEST_EQUAL(cache.Get("4").m_status, Cache::HotelStatus::Available, ()); } } // namespace diff --git a/map/map_tests/booking_filter_test.cpp b/map/map_tests/booking_filter_test.cpp index a50eb37988..30d7964830 100644 --- a/map/map_tests/booking_filter_test.cpp +++ b/map/map_tests/booking_filter_test.cpp @@ -114,9 +114,13 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_AvailabilitySmoke) rect, scales::GetUpperScale()); ParamsInternal filterParams; search::Results filteredResults; + std::vector availabilityExtras; filterParams.m_apiParams = std::make_shared(); - filterParams.m_callback = [&filteredResults](search::Results const & results) { + filterParams.m_callback = [&filteredResults, &availabilityExtras]( + search::Results && results, + std::vector && extras) { filteredResults = results; + availabilityExtras = extras; testing::Notify(); }; @@ -126,6 +130,8 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_AvailabilitySmoke) TEST_NOT_EQUAL(filteredResults.GetCount(), 0, ()); TEST_EQUAL(filteredResults.GetCount(), expectedResults.GetCount(), ()); + TEST(!availabilityExtras.empty(), ()); + TEST_EQUAL(availabilityExtras.size(), filteredResults.GetCount(), ()); for (size_t i = 0; i < filteredResults.GetCount(); ++i) { @@ -200,20 +206,27 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ProcessorSmoke) TasksInternal tasks; ParamsInternal availabilityParams; search::Results availabilityResults; + std::vector availabilityExtras; availabilityParams.m_apiParams = std::make_shared(); - availabilityParams.m_callback = [&availabilityResults](search::Results const & results) { + availabilityParams.m_callback = [&availabilityResults, &availabilityExtras]( + search::Results const & results, + std::vector && extras) { availabilityResults = results; + availabilityExtras = extras; }; tasks.emplace_back(Type::Availability, std::move(availabilityParams)); ParamsInternal dealsParams; search::Results dealsResults; + std::vector dealsExtras; booking::AvailabilityParams p; p.m_dealsOnly = true; dealsParams.m_apiParams = std::make_shared(p); - dealsParams.m_callback = [&dealsResults](search::Results const & results) { + dealsParams.m_callback = [&dealsResults, &dealsExtras](search::Results const & results, + std::vector && extras) { dealsResults = results; + dealsExtras = extras; testing::Notify(); }; @@ -227,6 +240,8 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ProcessorSmoke) TEST_NOT_EQUAL(availabilityResults.GetCount(), 0, ()); TEST_EQUAL(availabilityResults.GetCount(), expectedAvailabilityResults.GetCount(), ()); + TEST(!availabilityExtras.empty(), ()); + TEST_EQUAL(availabilityExtras.size(), availabilityResults.GetCount(), ()); for (size_t i = 0; i < availabilityResults.GetCount(); ++i) { @@ -236,6 +251,8 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ProcessorSmoke) TEST_NOT_EQUAL(dealsResults.GetCount(), 0, ()); TEST_EQUAL(dealsResults.GetCount(), expectedDealsResults.GetCount(), ()); + TEST(!dealsExtras.empty(), ()); + TEST_EQUAL(dealsExtras.size(), dealsResults.GetCount(), ()); for (size_t i = 0; i < dealsResults.GetCount(); ++i) { @@ -319,9 +336,13 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ApplyFilterOntoWithFeatureIds) ParamsRawInternal filterParams; std::vector filteredResults; + std::vector availabilityExtras; filterParams.m_apiParams = std::make_shared(); - filterParams.m_callback = [&filteredResults](std::vector const & results) { + filterParams.m_callback = [&filteredResults, &availabilityExtras]( + std::vector const & results, + std::vector && extras) { filteredResults = results; + availabilityExtras = extras; testing::Notify(); }; @@ -332,5 +353,7 @@ UNIT_CLASS_TEST(TestMwmEnvironment, BookingFilter_ApplyFilterOntoWithFeatureIds) TEST_NOT_EQUAL(filteredResults.size(), 0, ()); TEST_EQUAL(filteredResults.size(), expectedFeatureIds.size(), ()); TEST_EQUAL(filteredResults, expectedFeatureIds, ()); + TEST(!availabilityExtras.empty(), ()); + TEST_EQUAL(availabilityExtras.size(), filteredResults.size(), ()); } } // namespace diff --git a/partners_api/booking_api.cpp b/partners_api/booking_api.cpp index c87e032d4e..d3694a8be1 100644 --- a/partners_api/booking_api.cpp +++ b/partners_api/booking_api.cpp @@ -340,20 +340,24 @@ void FillBlocks(string const & src, string const & currency, Blocks & blocks) } } -void FillHotelIds(string const & src, vector & ids) +void FillHotels(string const & src, HotelsWithExtras & hotels) { base::Json root(src.c_str()); auto const resultsArray = json_object_get(root.get(), "result"); auto const size = json_array_size(resultsArray); - ids.resize(size); + hotels.reserve(size); for (size_t i = 0; i < size; ++i) { auto const obj = json_array_get(resultsArray, i); uint64_t id = 0; + Extras extras; FromJSONObject(obj, "hotel_id", id); - ids[i] = std::to_string(id); + FromJSONObject(obj, "price", extras.m_price); + FromJSONObject(obj, "hotel_currency_code", extras.m_currency); + + hotels.emplace(std::to_string(id), std::move(extras)); } } @@ -536,7 +540,7 @@ void Api::GetHotelAvailability(AvailabilityParams const & params, { GetPlatform().RunTask(Platform::Thread::Network, [params, fn]() { - std::vector result; + HotelsWithExtras result; string httpResult; if (!RawApi::HotelAvailability(params, httpResult)) { @@ -546,7 +550,7 @@ void Api::GetHotelAvailability(AvailabilityParams const & params, try { - FillHotelIds(httpResult, result); + FillHotels(httpResult, result); } catch (base::Json::Exception const & e) { diff --git a/partners_api/booking_api.hpp b/partners_api/booking_api.hpp index 507e4740fb..db7fd2f00d 100644 --- a/partners_api/booking_api.hpp +++ b/partners_api/booking_api.hpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include #include namespace booking @@ -107,6 +109,16 @@ struct Blocks std::vector m_blocks; }; +struct Extras +{ + Extras() = default; + Extras(double price, std::string const & currency) : m_price(price), m_currency(currency) {} + + double m_price = 0.0; + std::string m_currency; +}; + +using HotelsWithExtras = std::unordered_map; class RawApi { @@ -122,7 +134,7 @@ using BlockAvailabilityCallback = platform::SafeCallback; using GetHotelInfoCallback = platform::SafeCallback; // NOTE: this callback will be called on the network thread. -using GetHotelAvailabilityCallback = std::function hotelIds)>; +using GetHotelAvailabilityCallback = std::function; /// Callbacks will be called in the same order as methods are called. class Api diff --git a/xcode/map/map.xcodeproj/project.pbxproj b/xcode/map/map.xcodeproj/project.pbxproj index 0221eaac1a..804dd5bbdb 100644 --- a/xcode/map/map.xcodeproj/project.pbxproj +++ b/xcode/map/map.xcodeproj/project.pbxproj @@ -112,6 +112,8 @@ 3DD692B02209E272001C3C62 /* notification_manager_delegate.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DD692AE2209E272001C3C62 /* notification_manager_delegate.hpp */; }; 3DD692B12209E272001C3C62 /* notification_manager_delegate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DD692AF2209E272001C3C62 /* notification_manager_delegate.cpp */; }; 3DD692B3220AD240001C3C62 /* caching_address_getter.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DD692B2220AD240001C3C62 /* caching_address_getter.hpp */; }; + 3DE28A7824BE05220009465C /* booking_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DE28A7624BE05220009465C /* booking_utils.cpp */; }; + 3DE28A7924BE05220009465C /* booking_utils.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DE28A7724BE05220009465C /* booking_utils.hpp */; }; 3DEE1ADE21EE03B400054A91 /* power_management_schemas.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DEE1ADA21EE03B400054A91 /* power_management_schemas.hpp */; }; 3DEE1ADF21EE03B400054A91 /* power_manager.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3DEE1ADB21EE03B400054A91 /* power_manager.hpp */; }; 3DEE1AE021EE03B400054A91 /* power_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3DEE1ADC21EE03B400054A91 /* power_manager.cpp */; }; @@ -416,6 +418,8 @@ 3DD692AE2209E272001C3C62 /* notification_manager_delegate.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = notification_manager_delegate.hpp; sourceTree = ""; }; 3DD692AF2209E272001C3C62 /* notification_manager_delegate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = notification_manager_delegate.cpp; sourceTree = ""; }; 3DD692B2220AD240001C3C62 /* caching_address_getter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = caching_address_getter.hpp; sourceTree = ""; }; + 3DE28A7624BE05220009465C /* booking_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = booking_utils.cpp; sourceTree = ""; }; + 3DE28A7724BE05220009465C /* booking_utils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = booking_utils.hpp; sourceTree = ""; }; 3DEE1ADA21EE03B400054A91 /* power_management_schemas.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = power_management_schemas.hpp; sourceTree = ""; }; 3DEE1ADB21EE03B400054A91 /* power_manager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = power_manager.hpp; sourceTree = ""; }; 3DEE1ADC21EE03B400054A91 /* power_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = power_manager.cpp; sourceTree = ""; }; @@ -912,6 +916,8 @@ 675345BD1A4054AD00A0A8C3 /* map */ = { isa = PBXGroup; children = ( + 3DE28A7624BE05220009465C /* booking_utils.cpp */, + 3DE28A7724BE05220009465C /* booking_utils.hpp */, 3D089870247FF5FE00837783 /* layers_statistics.cpp */, 3D089871247FF5FE00837783 /* layers_statistics.hpp */, BB5FA776245AE2CF009A81A4 /* guides_marks.cpp */, @@ -1106,6 +1112,7 @@ 3D18DC3C22956DD100A583A6 /* framework_light_delegate.hpp in Headers */, 4564FA82209497A70043CCFB /* bookmark_catalog.hpp in Headers */, 3DA5714020B5CC80007BDE27 /* booking_filter_params.hpp in Headers */, + 3DE28A7924BE05220009465C /* booking_utils.hpp in Headers */, 3D47B2941F054BC5000828D2 /* taxi_delegate.hpp in Headers */, 3D47B2C81F20EF06000828D2 /* displayed_categories_modifiers.hpp in Headers */, 3DA5713F20B5CC80007BDE27 /* booking_availability_filter.hpp in Headers */, @@ -1367,6 +1374,7 @@ 45A2D9D51F7556EB003310A0 /* user.cpp in Sources */, 0C2B73DE1E92AB9900530BB8 /* local_ads_manager.cpp in Sources */, F6B283071C1B03320081957A /* gps_track_storage.cpp in Sources */, + 3DE28A7824BE05220009465C /* booking_utils.cpp in Sources */, 670E39401C46C5C700E9C0A6 /* gps_tracker.cpp in Sources */, BBA014B220754997007402E4 /* user_mark_id_storage.cpp in Sources */, 6753464A1A4054E800A0A8C3 /* bookmark.cpp in Sources */,