forked from organicmaps/organicmaps
[notifications] ugc rate notification business logic
This commit is contained in:
parent
052e75adda
commit
67d6885283
10 changed files with 524 additions and 62 deletions
|
@ -395,6 +395,7 @@ Framework::Framework(FrameworkParams const & params)
|
|||
, m_popularityLoader(m_model.GetDataSource())
|
||||
, m_purchase(std::make_unique<Purchase>())
|
||||
, m_tipsApi(static_cast<TipsApi::Delegate &>(*this))
|
||||
, m_notificationManager(static_cast<notifications::NotificationManager::Delegate &>(*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);
|
||||
}
|
||||
|
|
|
@ -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<search::CityFinder> m_cityFinder;
|
||||
|
|
|
@ -94,8 +94,7 @@ public:
|
|||
|
||||
if (request & REQUEST_TYPE_NOTIFICATION)
|
||||
{
|
||||
m_notificationManager = std::make_unique<notifications::NotificationManager>();
|
||||
m_notificationManager->Load();
|
||||
m_notificationManager = std::make_unique<lightweight::NotificationManager>();
|
||||
request ^= REQUEST_TYPE_NOTIFICATION;
|
||||
}
|
||||
|
||||
|
@ -123,7 +122,7 @@ private:
|
|||
std::unique_ptr<CountryInfoReader> m_countryInfoReader;
|
||||
std::unique_ptr<LocalAdsFeaturesReader> m_localAdsFeaturesReader;
|
||||
std::unique_ptr<Statistics> m_localAdsStatistics;
|
||||
std::unique_ptr<notifications::NotificationManager> m_notificationManager;
|
||||
std::unique_ptr<lightweight::NotificationManager> m_notificationManager;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -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<eye::MapObject>();
|
||||
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<eye::MapObject>();
|
||||
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<eye::MapObject>();
|
||||
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
|
||||
|
|
|
@ -4,9 +4,122 @@
|
|||
#include "map/notifications/notification_queue_storage.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
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<std::string, 4> 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<int8_t> queueFileData;
|
||||
|
@ -27,19 +140,47 @@ void NotificationManager::Load()
|
|||
}
|
||||
}
|
||||
|
||||
boost::optional<Notification> 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<NotificationCandidate> 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<eye::MapObject>(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
|
||||
|
|
|
@ -13,17 +13,60 @@ namespace notifications
|
|||
class NotificationManager : public eye::Subscriber
|
||||
{
|
||||
public:
|
||||
void Load();
|
||||
friend class NotificationManagerForTesting;
|
||||
|
||||
boost::optional<Notification> 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<NotificationCandidate> 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<notifications::NotificationCandidate> 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
|
||||
|
|
|
@ -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<eye::MapObject> m_mapObject;
|
||||
Time m_created;
|
||||
Time m_used;
|
||||
Time m_timeOfLastEvent;
|
||||
std::shared_ptr<eye::MapObject> m_mapObject;
|
||||
};
|
||||
|
||||
using Candidates = std::deque<Notification>;
|
||||
using Candidates = std::deque<NotificationCandidate>;
|
||||
|
||||
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
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "base/atomic_shared_ptr.hpp"
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -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:
|
||||
|
|
|
@ -219,6 +219,10 @@ public:
|
|||
MercatorBounds::ClampX(pos.x + 1e-7), MercatorBounds::ClampY(pos.y + 1e-7)};
|
||||
}
|
||||
|
||||
std::vector<std::string> const & GetMwmNames() const { return m_mwmNames; }
|
||||
|
||||
void SetMwmNames(std::vector<std::string> 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<std::string> 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 +
|
||||
|
|
Loading…
Add table
Reference in a new issue