diff --git a/map/framework.cpp b/map/framework.cpp index 1ceedde942..c9d5c9245d 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -395,6 +395,7 @@ Framework::Framework(FrameworkParams const & params) , m_popularityLoader(m_model.GetDataSource()) , m_purchase(std::make_unique()) , m_tipsApi(static_cast(*this)) + , m_notificationManager(static_cast(*this)) { CHECK(IsLittleEndian(), ("Only little-endian architectures are supported.")); @@ -511,6 +512,8 @@ Framework::Framework(FrameworkParams const & params) InitTransliteration(); LOG(LDEBUG, ("Transliterators initialized")); + m_notificationManager.Load(); + m_notificationManager.TrimExpired(); eye::Eye::Instance().TrimExpired(); eye::Eye::Instance().Subscribe(&m_notificationManager); } diff --git a/map/framework.hpp b/map/framework.hpp index 83ad4af907..884fe2274b 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -137,7 +137,8 @@ struct FrameworkParams class Framework : public SearchAPI::Delegate, public RoutingManager::Delegate, - public TipsApi::Delegate + public TipsApi::Delegate, + public notifications::NotificationManager::Delegate { DISALLOW_COPY(Framework); @@ -852,7 +853,7 @@ private: //@} public: - storage::TCountriesVec GetTopmostCountries(ms::LatLon const & latlon) const; + storage::TCountriesVec GetTopmostCountries(ms::LatLon const & latlon) const override; private: unique_ptr m_cityFinder; diff --git a/map/framework_light.hpp b/map/framework_light.hpp index c88f02e1cb..9d8487fb27 100644 --- a/map/framework_light.hpp +++ b/map/framework_light.hpp @@ -94,8 +94,7 @@ public: if (request & REQUEST_TYPE_NOTIFICATION) { - m_notificationManager = std::make_unique(); - m_notificationManager->Load(); + m_notificationManager = std::make_unique(); request ^= REQUEST_TYPE_NOTIFICATION; } @@ -123,7 +122,7 @@ private: std::unique_ptr m_countryInfoReader; std::unique_ptr m_localAdsFeaturesReader; std::unique_ptr m_localAdsStatistics; - std::unique_ptr m_notificationManager; + std::unique_ptr m_notificationManager; }; template<> diff --git a/map/map_tests/notification_tests.cpp b/map/map_tests/notification_tests.cpp index ea9e5a24c0..d7fd724e2b 100644 --- a/map/map_tests/notification_tests.cpp +++ b/map/map_tests/notification_tests.cpp @@ -1,5 +1,6 @@ #include "testing/testing.hpp" +#include "map/notifications/notification_manager.hpp" #include "map/notifications/notification_queue.hpp" #include "map/notifications/notification_queue_serdes.hpp" #include "map/notifications/notification_queue_storage.hpp" @@ -13,6 +14,20 @@ using namespace notifications; +namespace notifications +{ +class NotificationManagerForTesting : public NotificationManager +{ +public: + explicit NotificationManagerForTesting(NotificationManager::Delegate const & delegate) + : NotificationManager(delegate) + { + } + + Queue & GetEditableQueue() { return m_queue; } +}; +} // namespace notifications + namespace { class ScopedNotificationsQueue @@ -24,13 +39,28 @@ public: } }; +class DelegateForTesting : public NotificationManager::Delegate +{ +public: + // NotificationManager::Delegate overrides: + storage::TCountriesVec GetTopmostCountries(ms::LatLon const & latlon) const override + { + return m_countries; + } + + void SetCountries(storage::TCountriesVec const & countries) { m_countries = countries; } + +private: + storage::TCountriesVec m_countries; +}; + Queue MakeDefaultQueueForTesting() { Queue queue; { - Notification notification; - notification.m_type = Notification::Type::UgcReview; + NotificationCandidate notification; + notification.m_type = NotificationCandidate::Type::UgcReview; notification.m_mapObject = std::make_unique(); notification.m_mapObject->SetBestType("cafe"); @@ -41,8 +71,8 @@ Queue MakeDefaultQueueForTesting() } { - Notification notification; - notification.m_type = Notification::Type::UgcReview; + NotificationCandidate notification; + notification.m_type = NotificationCandidate::Type::UgcReview; notification.m_mapObject = std::make_unique(); notification.m_mapObject->SetBestType("shop"); @@ -53,8 +83,8 @@ Queue MakeDefaultQueueForTesting() } { - Notification notification; - notification.m_type = Notification::Type::UgcReview; + NotificationCandidate notification; + notification.m_type = NotificationCandidate::Type::UgcReview; notification.m_mapObject = std::make_unique(); notification.m_mapObject->SetBestType("viewpoint"); @@ -111,4 +141,170 @@ UNIT_CLASS_TEST(ScopedNotificationsQueue, Notifications_QueueSaveLoadTest) CompareWithDefaultQueue(result); } + +UNIT_CLASS_TEST(ScopedNotificationsQueue, Notifications_UgcRateCheckRouteToInSameGeoTrigger) +{ + DelegateForTesting delegate; + NotificationManagerForTesting notificationManager(delegate); + + delegate.SetCountries({"Norway_Central"}); + + eye::MapObject mapObject; + mapObject.SetPos(MercatorBounds::FromLatLon({59.909299, 10.769807})); + mapObject.SetReadableName("Visiting a Bjarne"); + mapObject.SetBestType("amenity-bar"); + mapObject.SetMwmNames({"Norway_Central"}); + + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::RouteToCreated; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 1, ()); + notificationManager.GetEditableQueue().m_candidates[0].m_timeOfLastEvent = event.m_eventTime; + + auto result = notificationManager.GetNotification(); + + TEST(result.is_initialized(), ()); + TEST_EQUAL(result.get().m_type, NotificationCandidate::Type::UgcReview, ()); + + result = notificationManager.GetNotification(); + TEST(!result.is_initialized(), ()); +} + +UNIT_CLASS_TEST(ScopedNotificationsQueue, Notifications_UgcRateCheckUgcNotSavedTrigger) +{ + DelegateForTesting delegate; + NotificationManagerForTesting notificationManager(delegate); + + delegate.SetCountries({"Norway_Central"}); + + eye::MapObject mapObject; + mapObject.SetPos(MercatorBounds::FromLatLon({59.909299, 10.769807})); + mapObject.SetReadableName("Visiting a Bjarne"); + mapObject.SetBestType("amenity-bar"); + mapObject.SetMwmNames({"Norway_Central"}); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::Open; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 0, ()); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::UgcEditorOpened; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 1, ()); + + auto result = notificationManager.GetNotification(); + + TEST(!result.is_initialized(), ()); + + notificationManager.GetEditableQueue().m_candidates[0].m_timeOfLastEvent = + notifications::Clock::now() - std::chrono::hours(25); + + result = notificationManager.GetNotification(); + + TEST(result.is_initialized(), ()); + TEST_EQUAL(result.get().m_type, NotificationCandidate::Type::UgcReview, ()); + + result = notificationManager.GetNotification(); + TEST(!result.is_initialized(), ()); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::UgcEditorOpened; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::UgcSaved; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + result = notificationManager.GetNotification(); + TEST(!result.is_initialized(), ()); +} + +UNIT_CLASS_TEST(ScopedNotificationsQueue, Notifications_UgcRateCheckPlannedTripTrigger) +{ + DelegateForTesting delegate; + NotificationManagerForTesting notificationManager(delegate); + + delegate.SetCountries({"Norway_Central"}); + + eye::MapObject mapObject; + mapObject.SetPos(MercatorBounds::FromLatLon({59.909299, 10.769807})); + mapObject.SetReadableName("Visiting a Bjarne"); + mapObject.SetBestType("amenity-bar"); + mapObject.SetMwmNames({"Norway_Central"}); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::Open; + event.m_userPos = MercatorBounds::FromLatLon({54.637300, 19.877731}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 0, ()); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::Open; + event.m_userPos = MercatorBounds::FromLatLon({54.637310, 19.877735}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 0, ()); + + { + eye::MapObject::Event event; + event.m_type = eye::MapObject::Event::Type::RouteToCreated; + event.m_userPos = MercatorBounds::FromLatLon({59.920333, 10.780793}); + event.m_eventTime = notifications::Clock::now() - std::chrono::hours(25); + mapObject.GetEditableEvents().push_back(event); + notificationManager.OnMapObjectEvent(mapObject); + } + + TEST_EQUAL(notificationManager.GetEditableQueue().m_candidates.size(), 1, ()); + + auto result = notificationManager.GetNotification(); + + TEST(!result.is_initialized(), ()); + + notificationManager.GetEditableQueue().m_candidates[0].m_timeOfLastEvent = + notifications::Clock::now() - std::chrono::hours(25); + + result = notificationManager.GetNotification(); + + TEST(result.is_initialized(), ()); + TEST_EQUAL(result.get().m_type, NotificationCandidate::Type::UgcReview, ()); + + result = notificationManager.GetNotification(); + TEST(!result.is_initialized(), ()); +} } // namespace diff --git a/map/notifications/notification_manager.cpp b/map/notifications/notification_manager.cpp index 8f508d8490..60eb6fd671 100644 --- a/map/notifications/notification_manager.cpp +++ b/map/notifications/notification_manager.cpp @@ -4,9 +4,122 @@ #include "map/notifications/notification_queue_storage.hpp" #include "base/logging.hpp" +#include "base/macros.hpp" + +#include +#include +#include + +using namespace notifications; + +namespace +{ +auto constexpr kCandidatesExpirePeriod = std::chrono::hours(24 * 30); +auto constexpr kPeriodBetweenNotifications = std::chrono::hours(24 * 7); +auto constexpr kMinTimeSinceLastEventForUgcRate = std::chrono::hours(24); + +std::array const kUgcRateSupportedTypes = {"amenity-bar", "amenity-cafe", + "amenity-pub", "amenity-restaurant"}; + +double constexpr kMinDistanceToTriggerUgcRateInMeters = 50000.0; // 50 km + +uint32_t constexpr kOpenCountForPlannedTripTrigger = 2; + +bool CheckUgcNotSavedTrigger(eye::MapObject const & poi) +{ + bool ugcEditorShowed = false; + bool ugcSaved = false; + for (auto const & event : poi.GetEvents()) + { + if (event.m_type == eye::MapObject::Event::Type::UgcEditorOpened && !ugcEditorShowed) + { + ugcEditorShowed = true; + } + else if (event.m_type == eye::MapObject::Event::Type::UgcSaved) + { + ugcSaved = true; + break; + } + } + + if (ugcEditorShowed && !ugcSaved) + return true; + + return false; +} + +bool IsSmallDistanceAndSameMwm(eye::MapObject const & poi, eye::MapObject::Event const & event, + storage::TCountriesVec const & userMwms) +{ + auto const distanceToUser = MercatorBounds::DistanceOnEarth(event.m_userPos, poi.GetPos()); + if (distanceToUser > kMinDistanceToTriggerUgcRateInMeters) + return false; + + auto const & poiMwms = poi.GetMwmNames(); + + return std::any_of(userMwms.cbegin(), userMwms.cend(), [&poiMwms](auto const & userMwm) + { + return std::find(poiMwms.cbegin(), poiMwms.cend(), userMwm) != poiMwms.cend(); + }); +} + +bool CheckRouteToInSameGeoTrigger(eye::MapObject const & poi, + storage::TCountriesVec const & userMwms) +{ + CHECK_GREATER(poi.GetEvents().size(), 0, ()); + + auto const & lastEvent = poi.GetEvents().back(); + + if (lastEvent.m_type != eye::MapObject::Event::Type::RouteToCreated) + return false; + + return IsSmallDistanceAndSameMwm(poi, lastEvent, userMwms); +} + +bool CheckPlannedTripTrigger(eye::MapObject const & poi, storage::TCountriesVec const & userMwms) +{ + CHECK_GREATER(poi.GetEvents().size(), 0, ()); + + auto const & events = poi.GetEvents(); + + if (events.back().m_type != eye::MapObject::Event::Type::Open) + return false; + + if (!IsSmallDistanceAndSameMwm(poi, events.back(), userMwms)) + return false; + + uint32_t openCounter = 0; + for (size_t i = 0; i < events.size() - 1; ++i) + { + if (events[i].m_type != eye::MapObject::Event::Type::Open && + events[i].m_type != eye::MapObject::Event::Type::AddToBookmark && + events[i].m_type != eye::MapObject::Event::Type::RouteToCreated) + { + continue; + } + + if (IsSmallDistanceAndSameMwm(poi, events[i], userMwms)) + continue; + + if (events[i].m_type == eye::MapObject::Event::Type::Open) + ++openCounter; + else + return true; + + return openCounter >= kOpenCountForPlannedTripTrigger; + } + + return false; +} +} // namespace namespace notifications { +NotificationManager::NotificationManager(NotificationManager::Delegate const & delegate) + : m_delegate(delegate) +{ +} + void NotificationManager::Load() { std::vector queueFileData; @@ -27,19 +140,47 @@ void NotificationManager::Load() } } -boost::optional NotificationManager::GetNotification() const +void NotificationManager::TrimExpired() { - if (m_queue.m_candidates.empty()) + auto & candidates = m_queue.m_candidates; + candidates.erase(std::remove_if(candidates.begin(), candidates.end(), [](auto const & item) + { + if (item.m_used.time_since_epoch().count() != 0) + return Clock::now() - item.m_used >= eye::Eye::GetMapObjectEventsExpirePeriod(); + + return Clock::now() - item.m_created >= kCandidatesExpirePeriod; + + }), candidates.end()); + + VERIFY(Save(), ()); +} + +boost::optional NotificationManager::GetNotification() +{ + if (Clock::now() - m_queue.m_lastNotificationProvidedTime < kPeriodBetweenNotifications) return {}; - // Is not implemented yet. Coming soon. + auto & candidates = m_queue.m_candidates; - return {}; + if (candidates.empty()) + return {}; + + auto it = GetUgcRateCandidate(); + + if (it == candidates.end()) + return {}; + + it->m_used = Clock::now(); + m_queue.m_lastNotificationProvidedTime = Clock::now(); + + VERIFY(Save(), ()); + + return *it; } void NotificationManager::OnMapObjectEvent(eye::MapObject const & poi) { - // Is not implemented yet. Coming soon. + ProcessUgcRateCandidates(poi); } bool NotificationManager::Save() @@ -48,4 +189,76 @@ bool NotificationManager::Save() QueueSerdes::Serialize(m_queue, fileData); return QueueStorage::Save(fileData); } + +void NotificationManager::ProcessUgcRateCandidates(eye::MapObject const & poi) +{ + if (poi.GetReadableName().empty()) + return; + + { + auto const it = std::find(kUgcRateSupportedTypes.cbegin(), kUgcRateSupportedTypes.cend(), + poi.GetBestType()); + if (it == kUgcRateSupportedTypes.cend()) + return; + } + + auto it = m_queue.m_candidates.begin(); + for (; it != m_queue.m_candidates.end(); ++it) + { + if (it->m_type != NotificationCandidate::Type::UgcReview || !it->m_mapObject || + it->m_used.time_since_epoch().count() != 0) + { + continue; + } + + if (it->m_mapObject->AlmostEquals(poi)) + { + if (poi.GetEvents().back().m_type == eye::MapObject::Event::Type::UgcSaved && + CheckUgcNotSavedTrigger(poi)) + { + m_queue.m_candidates.erase(it); + VERIFY(Save(), ()); + break; + } + + it->m_timeOfLastEvent = Clock::now(); + VERIFY(Save(), ()); + return; + } + } + + CHECK_GREATER(poi.GetEvents().size(), 0, ()); + + auto const latLon = MercatorBounds::ToLatLon(poi.GetEvents().back().m_userPos); + auto const userMwms = m_delegate.GetTopmostCountries(std::move(latLon)); + + if (CheckUgcNotSavedTrigger(poi) || CheckRouteToInSameGeoTrigger(poi, userMwms) || + CheckPlannedTripTrigger(poi, userMwms)) + { + NotificationCandidate candidate; + candidate.m_type = NotificationCandidate::Type::UgcReview; + candidate.m_created = Clock::now(); + candidate.m_timeOfLastEvent = candidate.m_created; + candidate.m_mapObject = std::make_shared(poi); + m_queue.m_candidates.emplace_back(std::move(candidate)); + + VERIFY(Save(), ()); + } +} + +Candidates::iterator NotificationManager::GetUgcRateCandidate() +{ + auto it = m_queue.m_candidates.begin(); + for (; it != m_queue.m_candidates.end(); ++it) + { + if (it->m_used.time_since_epoch().count() == 0 && + it->m_type == NotificationCandidate::Type::UgcReview && + Clock::now() - it->m_timeOfLastEvent >= kMinTimeSinceLastEventForUgcRate) + { + return it; + } + } + + return it; +} } // namespace notifications diff --git a/map/notifications/notification_manager.hpp b/map/notifications/notification_manager.hpp index ae8f7e41ca..2857332a38 100644 --- a/map/notifications/notification_manager.hpp +++ b/map/notifications/notification_manager.hpp @@ -13,17 +13,60 @@ namespace notifications class NotificationManager : public eye::Subscriber { public: - void Load(); + friend class NotificationManagerForTesting; - boost::optional GetNotification() const; + class Delegate + { + public: + virtual ~Delegate() = default; + + virtual storage::TCountriesVec GetTopmostCountries(ms::LatLon const & latlon) const = 0; + }; + + explicit NotificationManager(Delegate const & delegate); + + void Load(); + void TrimExpired(); + + boost::optional GetNotification(); // eye::Subscriber overrides: void OnMapObjectEvent(eye::MapObject const & poi) override; private: bool Save(); + void ProcessUgcRateCandidates(eye::MapObject const & poi); + Candidates::iterator GetUgcRateCandidate(); + Delegate const & m_delegate; // Notification candidates queue. Queue m_queue; }; } // namespace notifications +namespace lightweight +{ +class NotificationManager +{ +public: + NotificationManager() : m_manager(m_delegate) { m_manager.Load(); } + + boost::optional GetNotification() + { + return m_manager.GetNotification(); + } + +private: + class EmptyDelegate : public notifications::NotificationManager::Delegate + { + public: + // NotificationManager::Delegate overrides: + storage::TCountriesVec GetTopmostCountries(ms::LatLon const & latlon) const override + { + return {}; + } + }; + + EmptyDelegate m_delegate; + notifications::NotificationManager m_manager; +}; +} // namespace lightweight diff --git a/map/notifications/notification_queue.hpp b/map/notifications/notification_queue.hpp index 6f13b8c276..ec43715b5d 100644 --- a/map/notifications/notification_queue.hpp +++ b/map/notifications/notification_queue.hpp @@ -8,7 +8,10 @@ namespace notifications { -struct Notification +using Clock = std::chrono::system_clock; +using Time = Clock::time_point; + +struct NotificationCandidate { enum class Type : uint8_t { @@ -16,13 +19,17 @@ struct Notification UgcReview }; - DECLARE_VISITOR(visitor(m_type, "type"), visitor(m_mapObject, "object")); + DECLARE_VISITOR(visitor(m_type, "type"), visitor(m_created, "created_time"), + visitor(m_used, "used"), visitor(m_mapObject, "object")); Type m_type; - std::unique_ptr m_mapObject; + Time m_created; + Time m_used; + Time m_timeOfLastEvent; + std::shared_ptr m_mapObject; }; -using Candidates = std::deque; +using Candidates = std::deque; enum class Version : int8_t { @@ -35,19 +42,20 @@ struct QueueV0 { static Version GetVersion() { return Version::V0; } - DECLARE_VISITOR(visitor(m_candidates, "queue")) + DECLARE_VISITOR(visitor(m_candidates, "queue")); + Time m_lastNotificationProvidedTime; Candidates m_candidates; }; using Queue = QueueV0; -inline std::string DebugPrint(Notification::Type type) +inline std::string DebugPrint(NotificationCandidate::Type type) { switch (type) { - case Notification::Type::UgcAuth: return "UgcAuth"; - case Notification::Type::UgcReview: return "UgcReview"; + case NotificationCandidate::Type::UgcAuth: return "UgcAuth"; + case NotificationCandidate::Type::UgcReview: return "UgcReview"; } } } // namespace notifications diff --git a/metrics/eye.cpp b/metrics/eye.cpp index 173bf29b03..a889b44ff8 100644 --- a/metrics/eye.cpp +++ b/metrics/eye.cpp @@ -10,7 +10,6 @@ #include "base/logging.hpp" #include -#include #include #include #include @@ -105,6 +104,12 @@ void Eye::UnsubscribeAll() m_subscribers.clear(); } +// static +std::chrono::hours const & Eye::GetMapObjectEventsExpirePeriod() +{ + return kMapObjectEventsExpirePeriod; +} + void Eye::TrimExpired() { GetPlatform().RunTask(Platform::Thread::File, [this] @@ -417,56 +422,47 @@ void Eye::Event::LayerShown(Layer::Type type) } // static -void Eye::Event::PlacePageOpened(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos) +void Eye::Event::PlacePageOpened(MapObject const & mapObject, m2::PointD const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [bestType, pos, readableName, userPos] + GetPlatform().RunTask(Platform::Thread::File, [mapObject, userPos] { - Instance().RegisterMapObjectEvent({bestType, pos, readableName}, MapObject::Event::Type::Open, userPos); + Instance().RegisterMapObjectEvent(mapObject, MapObject::Event::Type::Open, userPos); }); } // static -void Eye::Event::UgcEditorOpened(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos) +void Eye::Event::UgcEditorOpened(MapObject const & mapObject, m2::PointD const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [bestType, pos, readableName, userPos] + GetPlatform().RunTask(Platform::Thread::File, [mapObject, userPos] { - Instance().RegisterMapObjectEvent({bestType, pos, readableName}, MapObject::Event::Type::UgcEditorOpened, - userPos); + Instance().RegisterMapObjectEvent(mapObject, MapObject::Event::Type::UgcEditorOpened, userPos); }); } // static -void Eye::Event::UgcSaved(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos) +void Eye::Event::UgcSaved(MapObject const & mapObject, m2::PointD const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [bestType, pos, readableName, userPos] + GetPlatform().RunTask(Platform::Thread::File, [mapObject, userPos] { - Instance().RegisterMapObjectEvent({bestType, pos, readableName}, MapObject::Event::Type::UgcSaved, - userPos); + Instance().RegisterMapObjectEvent(mapObject, MapObject::Event::Type::UgcSaved, userPos); }); } // static -void Eye::Event::AddToBookmarkClicked(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos) +void Eye::Event::AddToBookmarkClicked(MapObject const & mapObject, m2::PointD const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [bestType, pos, readableName, userPos] + GetPlatform().RunTask(Platform::Thread::File, [mapObject, userPos] { - Instance().RegisterMapObjectEvent({bestType, pos, readableName}, MapObject::Event::Type::AddToBookmark, - userPos); + Instance().RegisterMapObjectEvent(mapObject, MapObject::Event::Type::AddToBookmark, userPos); }); } // static -void Eye::Event::RouteCreatedToObject(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos) +void Eye::Event::RouteCreatedToObject(MapObject const & mapObject, m2::PointD const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [bestType, pos, readableName, userPos] + GetPlatform().RunTask(Platform::Thread::File, [mapObject, userPos] { - Instance().RegisterMapObjectEvent({bestType, pos, readableName}, MapObject::Event::Type::RouteToCreated, - userPos); + Instance().RegisterMapObjectEvent(mapObject, MapObject::Event::Type::RouteToCreated, userPos); }); } } // namespace eye diff --git a/metrics/eye.hpp b/metrics/eye.hpp index 1ff68a1bd9..fe22baf7bd 100644 --- a/metrics/eye.hpp +++ b/metrics/eye.hpp @@ -5,6 +5,7 @@ #include "base/atomic_shared_ptr.hpp" #include "base/macros.hpp" +#include #include #include @@ -44,16 +45,11 @@ public: static void DiscoveryShown(); static void DiscoveryItemClicked(Discovery::Event event); static void LayerShown(Layer::Type type); - static void PlacePageOpened(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos); - static void UgcEditorOpened(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos); - static void UgcSaved(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos); - static void AddToBookmarkClicked(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos); - static void RouteCreatedToObject(std::string const & bestType, m2::PointD const & pos, - std::string const & readableName, m2::PointD const & userPos); + static void PlacePageOpened(MapObject const & mapObject, m2::PointD const & userPos); + static void UgcEditorOpened(MapObject const & mapObject, m2::PointD const & userPos); + static void UgcSaved(MapObject const & mapObject, m2::PointD const & userPos); + static void AddToBookmarkClicked(MapObject const & mapObject, m2::PointD const & userPos); + static void RouteCreatedToObject(MapObject const & mapObject, m2::PointD const & userPos); }; static Eye & Instance(); @@ -64,6 +60,7 @@ public: void Subscribe(Subscriber * subscriber); void UnsubscribeAll(); + static std::chrono::hours const & GetMapObjectEventsExpirePeriod(); void TrimExpired(); private: diff --git a/metrics/eye_info.hpp b/metrics/eye_info.hpp index 89613ba0aa..23142d6fee 100644 --- a/metrics/eye_info.hpp +++ b/metrics/eye_info.hpp @@ -219,6 +219,10 @@ public: MercatorBounds::ClampX(pos.x + 1e-7), MercatorBounds::ClampY(pos.y + 1e-7)}; } + std::vector const & GetMwmNames() const { return m_mwmNames; } + + void SetMwmNames(std::vector const & ids) { m_mwmNames = ids; } + std::string const & GetReadableName() const { return m_readableName; } void SetReadableName(std::string const & readableName) { m_readableName = readableName; } @@ -230,11 +234,13 @@ public: m2::RectD GetLimitRect() const { return m_limitRect; } DECLARE_VISITOR(visitor(m_bestType, "type"), visitor(m_pos, "pos"), - visitor(m_readableName, "name"), visitor(m_events, "events")); + visitor(m_mwmNames, "mwm_names"), visitor(m_readableName, "name"), + visitor(m_events, "events")); private: std::string m_bestType; m2::PointD m_pos; + std::vector m_mwmNames; std::string m_readableName; // Mutable because of interface of the m4::Tree provides constant references in ForEach methods, // but we need to add events into existing objects to avoid some overhead (copy + change +