diff --git a/metrics/eye.cpp b/metrics/eye.cpp index 909ec5b98d..c91b679a54 100644 --- a/metrics/eye.cpp +++ b/metrics/eye.cpp @@ -10,6 +10,7 @@ #include "base/logging.hpp" #include +#include #include #include #include @@ -18,6 +19,9 @@ using namespace eye; namespace { +// Three months. +auto constexpr kMapObjectEventsExpirePeriod = std::chrono::hours(24 * 30 * 3); + void Load(Info & info) { Storage::Migrate(); @@ -49,22 +53,20 @@ bool Save(Info const & info) return Storage::SaveInfo(fileData); } -// TODO: use to trim old map object events. -// bool SaveMapObjects(Info const & info) -//{ -// std::vector fileData; -// Serdes::SerializeMapObjects(info.m_mapObjects, fileData); -// return Storage::SaveMapObjects(fileData); -//} -// -// TODO: use it to save map object events with append only flag. -// bool SaveMapObjectEvent(MapObject const & mapObject, MapObject::Event const & event) -//{ -// std::vector eventData; -// Serdes::SerializeMapObjectEvent(mapObject, event, eventData); -// -// return Storage::AppendMapObjectEvent(eventData); -//} +bool SaveMapObjects(MapObjects const & mapObjects) +{ + std::vector fileData; + Serdes::SerializeMapObjects(mapObjects, fileData); + return Storage::SaveMapObjects(fileData); +} + +bool SaveMapObjectEvent(MapObject const & mapObject, MapObject::Event const & event) +{ + std::vector eventData; + Serdes::SerializeMapObjectEvent(mapObject, event, eventData); + + return Storage::AppendMapObjectEvent(eventData); +} } // namespace namespace eye @@ -74,6 +76,11 @@ Eye::Eye() Info info; Load(info); m_info.Set(std::make_shared(info)); + + GetPlatform().RunTask(Platform::Thread::File, [this] + { + TrimExpiredMapObjectEvents(); + }); } // static @@ -107,6 +114,37 @@ bool Eye::Save(InfoType const & info) return true; } +void Eye::TrimExpiredMapObjectEvents() +{ + auto const info = m_info.Get(); + auto editableInfo = std::make_shared(*info); + auto changed = false; + + for (auto it = editableInfo->m_mapObjects.begin(); it != editableInfo->m_mapObjects.end();) + { + auto & events = it->second; + events.erase(std::remove_if(events.begin(), events.end(), [&changed](auto const & item) + { + if (Clock::now() - item.m_eventTime >= kMapObjectEventsExpirePeriod) + { + if (!changed) + changed = true; + + return true; + } + return false; + }), events.end()); + + if (events.empty()) + it = editableInfo->m_mapObjects.erase(it); + else + ++it; + } + + if (changed && SaveMapObjects(editableInfo->m_mapObjects)) + m_info.Set(editableInfo); +} + void Eye::RegisterTipClick(Tip::Type type, Tip::Event event) { auto const info = m_info.Get(); @@ -265,29 +303,42 @@ void Eye::RegisterLayerShown(Layer::Type type) }); } -void Eye::RegisterPlacePageOpened() +void Eye::RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, + ms::LatLon const & userPos) { + auto const info = m_info.Get(); + auto editableInfo = std::make_shared(*info); + auto & mapObjects = editableInfo->m_mapObjects; -} + MapObject::Event event; + event.m_type = type; + event.m_userPos = userPos; + event.m_eventTime = Clock::now(); -void Eye::RegisterUgcEditorOpened() -{ + MapObject::Events events; + auto it = mapObjects.find(mapObject); + if (it == mapObjects.end()) + { + events = {event}; + mapObjects.emplace(mapObject, std::move(events)); + } + else + { + it->second.push_back(event); + events = it->second; + } -} - -void Eye::RegisterUgcSaved() -{ - -} - -void Eye::RegisterAddToBookmarkClicked() -{ - -} - -void Eye::RegisterRouteCreatedToObject() -{ + if (!SaveMapObjectEvent(mapObject, event)) + return; + m_info.Set(editableInfo); + GetPlatform().RunTask(Platform::Thread::Gui, [this, mapObject, events] + { + for (auto subscriber : m_subscribers) + { + subscriber->OnMapObjectEvent(mapObject, events); + } + }); } // Eye::Event methods ------------------------------------------------------------------------------ @@ -346,47 +397,56 @@ void Eye::Event::LayerShown(Layer::Type type) } // static -void Eye::Event::PlacePageOpened() +void Eye::Event::PlacePageOpened(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [] + GetPlatform().RunTask(Platform::Thread::File, [bestType, latLon, userPos] { - Instance().RegisterPlacePageOpened(); + Instance().RegisterMapObjectEvent({bestType, latLon}, MapObject::Event::Type::Open, userPos); }); } // static -void Eye::Event::UgcEditorOpened() +void Eye::Event::UgcEditorOpened(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [] + GetPlatform().RunTask(Platform::Thread::File, [bestType, latLon, userPos] { - Instance().RegisterUgcEditorOpened(); + Instance().RegisterMapObjectEvent({bestType, latLon}, MapObject::Event::Type::UgcEditorOpened, + userPos); }); } // static -void Eye::Event::UgcSaved() +void Eye::Event::UgcSaved(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [] + GetPlatform().RunTask(Platform::Thread::File, [bestType, latLon, userPos] { - Instance().RegisterUgcSaved(); + Instance().RegisterMapObjectEvent({bestType, latLon}, MapObject::Event::Type::UgcSaved, + userPos); }); } // static -void Eye::Event::AddToBookmarkClicked() +void Eye::Event::AddToBookmarkClicked(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [] + GetPlatform().RunTask(Platform::Thread::File, [bestType, latLon, userPos] { - Instance().RegisterAddToBookmarkClicked(); + Instance().RegisterMapObjectEvent({bestType, latLon}, MapObject::Event::Type::AddToBookmark, + userPos); }); } // static -void Eye::Event::RouteCreatedToObject() +void Eye::Event::RouteCreatedToObject(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos) { - GetPlatform().RunTask(Platform::Thread::File, [] + GetPlatform().RunTask(Platform::Thread::File, [bestType, latLon, userPos] { - Instance().RegisterRouteCreatedToObject(); + Instance().RegisterMapObjectEvent({bestType, latLon}, MapObject::Event::Type::RouteToCreated, + userPos); }); } } // namespace eye diff --git a/metrics/eye.hpp b/metrics/eye.hpp index 3ca68ef44b..b603973fa8 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 namespace eye @@ -21,7 +22,7 @@ public: virtual void OnDiscoveryShown(Time const & time) {} virtual void OnDiscoveryItemClicked(Discovery::Event event) {} virtual void OnLayerShown(Layer const & layer) {} - virtual void OnPlacePageOpened(MapObject const & poi) {} + virtual void OnMapObjectEvent(MapObject const & poi, MapObject::Events const & events) {} }; // Note This class IS thread-safe. @@ -43,11 +44,16 @@ public: static void DiscoveryShown(); static void DiscoveryItemClicked(Discovery::Event event); static void LayerShown(Layer::Type type); - static void PlacePageOpened(); - static void UgcEditorOpened(); - static void UgcSaved(); - static void AddToBookmarkClicked(); - static void RouteCreatedToObject(); + static void PlacePageOpened(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos); + static void UgcEditorOpened(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos); + static void UgcSaved(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos); + static void AddToBookmarkClicked(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos); + static void RouteCreatedToObject(std::string const & bestType, ms::LatLon const & latLon, + ms::LatLon const & userPos); }; static Eye & Instance(); @@ -62,6 +68,7 @@ private: Eye(); bool Save(InfoType const & info); + void TrimExpiredMapObjectEvents(); // Event processing: void RegisterTipClick(Tip::Type type, Tip::Event event); @@ -70,11 +77,8 @@ private: void UpdateDiscoveryShownTime(); void IncrementDiscoveryItem(Discovery::Event event); void RegisterLayerShown(Layer::Type type); - void RegisterPlacePageOpened(); - void RegisterUgcEditorOpened(); - void RegisterUgcSaved(); - void RegisterAddToBookmarkClicked(); - void RegisterRouteCreatedToObject(); + void RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, + ms::LatLon const & userPos); base::AtomicSharedPtr m_info; std::vector m_subscribers; diff --git a/metrics/eye_info.hpp b/metrics/eye_info.hpp index fa7a53333a..3873e5b521 100644 --- a/metrics/eye_info.hpp +++ b/metrics/eye_info.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +182,8 @@ struct MapObject Time m_eventTime; }; + using Events = std::deque; + struct Hash { size_t operator()(MapObject const & p) const @@ -199,7 +202,7 @@ struct MapObject ms::LatLon m_pos; }; -using MapObjects = std::unordered_map, MapObject::Hash>; +using MapObjects = std::unordered_map; struct InfoV0 { diff --git a/metrics/eye_serdes.cpp b/metrics/eye_serdes.cpp index bda91797ba..b46132269f 100644 --- a/metrics/eye_serdes.cpp +++ b/metrics/eye_serdes.cpp @@ -123,7 +123,7 @@ void Serdes::DeserializeMapObjects(std::vector const & bytes, MapObjects auto it = result.find(poi); if (it == result.end()) { - std::vector events = {event.m_event}; + MapObject::Events events = {event.m_event}; result.emplace(poi, std::move(events)); } else diff --git a/metrics/metrics_tests/eye_tests.cpp b/metrics/metrics_tests/eye_tests.cpp index 0c0ddd324e..4ef630b244 100644 --- a/metrics/metrics_tests/eye_tests.cpp +++ b/metrics/metrics_tests/eye_tests.cpp @@ -7,6 +7,7 @@ #include "metrics/metrics_tests_support/eye_for_testing.hpp" +#include #include #include #include @@ -43,7 +44,7 @@ Info MakeDefaultInfoForTesting() poi.m_bestType = "shop"; poi.m_pos = {53.652007, 108.143443}; MapObject::Event eventInfo; - std::vector events; + MapObject::Events events; eventInfo.m_eventTime = Time(std::chrono::hours(90000)); eventInfo.m_userPos = {72.045507, 81.408095}; eventInfo.m_type = MapObject::Event::Type::AddToBookmark; @@ -385,3 +386,216 @@ UNIT_CLASS_TEST(ScopedEyeForTesting, AppendLayerTest) TEST_NOT_EQUAL(prevShowTime, lastShownLayerTime, ()); } } + +UNIT_CLASS_TEST(ScopedEyeForTesting, TrimExpiredMapObjectEvents) +{ + Info info; + { + MapObject poi; + poi.m_bestType = "shop"; + poi.m_pos = {53.652007, 108.143443}; + MapObject::Event eventInfo; + MapObject::Events events; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours((24 * 30 * 3) + 1); + eventInfo.m_userPos = {72.045507, 81.408095}; + eventInfo.m_type = MapObject::Event::Type::Open; + events.emplace_back(eventInfo); + + eventInfo.m_eventTime = + Clock::now() - (std::chrono::hours(24 * 30 * 3) + std::chrono::seconds(1)); + eventInfo.m_userPos = {72.045400, 81.408200}; + eventInfo.m_type = MapObject::Event::Type::AddToBookmark; + events.emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {72.045450, 81.408201}; + eventInfo.m_type = MapObject::Event::Type::RouteToCreated; + events.emplace_back(eventInfo); + + info.m_mapObjects.emplace(poi, events); + } + + { + MapObject poi; + poi.m_bestType = "cafe"; + poi.m_pos = {53.652005, 108.143448}; + MapObject::Event eventInfo; + MapObject::Events events; + + eventInfo.m_eventTime = Clock::now() - std::chrono::hours(24 * 30 * 3); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::Open; + events.emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now() - std::chrono::seconds(30); + eventInfo.m_userPos = {53.016347, 158.683327}; + eventInfo.m_type = MapObject::Event::Type::UgcEditorOpened; + events.emplace_back(eventInfo); + + eventInfo.m_eventTime = Clock::now(); + eventInfo.m_userPos = {53.116347, 158.783327}; + eventInfo.m_type = MapObject::Event::Type::UgcSaved; + events.emplace_back(eventInfo); + + info.m_mapObjects.emplace(poi, events); + } + + EyeForTesting::SetInfo(info); + + { + auto const resultInfo = Eye::Instance().GetInfo(); + auto const & mapObjects = resultInfo->m_mapObjects; + TEST_EQUAL(mapObjects.size(), 2, ()); + + { + MapObject poi; + poi.m_bestType = "shop"; + poi.m_pos = {53.652007, 108.143443}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 3, ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::Open, ()); + TEST_EQUAL(it->second[1].m_userPos, ms::LatLon(72.045400, 81.408200), ()); + TEST_EQUAL(it->second[2].m_userPos, ms::LatLon(72.045450, 81.408201), ()); + } + + { + MapObject poi; + poi.m_bestType = "cafe"; + poi.m_pos = {53.652005, 108.143448}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 3, ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::Open, ()); + TEST_EQUAL(it->second[1].m_userPos, ms::LatLon(53.016347, 158.683327), ()); + TEST_EQUAL(it->second[2].m_userPos, ms::LatLon(53.116347, 158.783327), ()); + } + } + + EyeForTesting::TrimExpiredMapObjectEvents(); + + { + auto const resultInfo = Eye::Instance().GetInfo(); + auto const & mapObjects = resultInfo->m_mapObjects; + TEST_EQUAL(mapObjects.size(), 1, ()); + + { + MapObject poi; + poi.m_bestType = "shop"; + poi.m_pos = {53.652007, 108.143443}; + + auto const it = mapObjects.find(poi); + + TEST(it == mapObjects.end(), ()); + } + + { + MapObject poi; + poi.m_bestType = "cafe"; + poi.m_pos = {53.652005, 108.143448}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 2, ()); + TEST_EQUAL(it->second[0].m_userPos, ms::LatLon(53.016347, 158.683327), ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::UgcEditorOpened, ()); + + TEST_EQUAL(it->second[1].m_userPos, ms::LatLon(53.116347, 158.783327), ()); + TEST_EQUAL(it->second[1].m_type, MapObject::Event::Type::UgcSaved, ()); + } + } +} + +UNIT_CLASS_TEST(ScopedEyeForTesting, RegisterMapObjectEvent) +{ + { + MapObject poi; + poi.m_bestType = "cafe"; + poi.m_pos = {53.652005, 108.143448}; + ms::LatLon userPos = {53.016347, 158.683327}; + + EyeForTesting::RegisterMapObjectEvent(poi, MapObject::Event::Type::Open, userPos); + + userPos = {53.016345, 158.683329}; + + EyeForTesting::RegisterMapObjectEvent(poi, MapObject::Event::Type::RouteToCreated, userPos); + } + { + MapObject poi; + poi.m_bestType = "shop"; + poi.m_pos = {53.652005, 108.143448}; + ms::LatLon userPos = {0.0, 0.0}; + + EyeForTesting::RegisterMapObjectEvent(poi, MapObject::Event::Type::RouteToCreated, userPos); + + userPos = {158.016345, 53.683329}; + + EyeForTesting::RegisterMapObjectEvent(poi, MapObject::Event::Type::AddToBookmark, userPos); + } + + { + MapObject poi; + poi.m_bestType = "amenity-bench"; + poi.m_pos = {53.652005, 108.143448}; + ms::LatLon userPos = {0.0, 0.0}; + + EyeForTesting::RegisterMapObjectEvent(poi, MapObject::Event::Type::Open, userPos); + } + + { + auto const resultInfo = Eye::Instance().GetInfo(); + auto const & mapObjects = resultInfo->m_mapObjects; + TEST_EQUAL(mapObjects.size(), 3, ()); + + { + MapObject poi; + poi.m_bestType = "cafe"; + poi.m_pos = {53.652005, 108.143448}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 2, ()); + TEST_EQUAL(it->second[0].m_userPos, ms::LatLon(53.016347, 158.683327), ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::Open, ()); + + TEST_EQUAL(it->second[1].m_userPos, ms::LatLon(53.016345, 158.683329), ()); + TEST_EQUAL(it->second[1].m_type, MapObject::Event::Type::RouteToCreated, ()); + } + + { + MapObject poi; + poi.m_bestType = "shop"; + poi.m_pos = {53.652005, 108.143448}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 2, ()); + TEST_EQUAL(it->second[0].m_userPos, ms::LatLon(0.0, 0.0), ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::RouteToCreated, ()); + + TEST_EQUAL(it->second[1].m_userPos, ms::LatLon(158.016345, 53.683329), ()); + TEST_EQUAL(it->second[1].m_type, MapObject::Event::Type::AddToBookmark, ()); + } + + { + MapObject poi; + poi.m_bestType = "amenity-bench"; + poi.m_pos = {53.652005, 108.143448}; + + auto const it = mapObjects.find(poi); + + TEST(it != mapObjects.end(), ()); + TEST_EQUAL(it->second.size(), 1, ()); + TEST_EQUAL(it->second[0].m_userPos, ms::LatLon(0.0, 0.0), ()); + TEST_EQUAL(it->second[0].m_type, MapObject::Event::Type::Open, ()); + } + } +} diff --git a/metrics/metrics_tests_support/eye_for_testing.cpp b/metrics/metrics_tests_support/eye_for_testing.cpp index b9bb5b5de1..cc6abb1776 100644 --- a/metrics/metrics_tests_support/eye_for_testing.cpp +++ b/metrics/metrics_tests_support/eye_for_testing.cpp @@ -71,4 +71,17 @@ void EyeForTesting::AppendLayer(Layer::Type type) { Eye::Instance().RegisterLayerShown(type); } + +// static +void EyeForTesting::TrimExpiredMapObjectEvents() +{ + Eye::Instance().TrimExpiredMapObjectEvents(); +} + +// static +void EyeForTesting::RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, + ms::LatLon const & userPos) +{ + Eye::Instance().RegisterMapObjectEvent(mapObject, type, userPos); +} } // namespace eye diff --git a/metrics/metrics_tests_support/eye_for_testing.hpp b/metrics/metrics_tests_support/eye_for_testing.hpp index cf171eb523..5180e1ccea 100644 --- a/metrics/metrics_tests_support/eye_for_testing.hpp +++ b/metrics/metrics_tests_support/eye_for_testing.hpp @@ -17,6 +17,9 @@ public: static void UpdateDiscoveryShownTime(); static void IncrementDiscoveryItem(Discovery::Event event); static void AppendLayer(Layer::Type type); + static void TrimExpiredMapObjectEvents(); + static void RegisterMapObjectEvent(MapObject const & mapObject, MapObject::Event::Type type, + ms::LatLon const & userPos); }; class ScopedEyeForTesting