| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/feature_engagement/internal/event_model_impl.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/feature_engagement/internal/event_model.h" |
| #include "components/feature_engagement/internal/event_storage_validator.h" |
| #include "components/feature_engagement/internal/event_store.h" |
| |
| namespace feature_engagement { |
| |
| EventModelImpl::EventModelImpl( |
| std::unique_ptr<EventStore> store, |
| std::unique_ptr<EventStorageValidator> storage_validator) |
| : store_(std::move(store)), |
| storage_validator_(std::move(storage_validator)), |
| ready_(false) {} |
| |
| EventModelImpl::~EventModelImpl() = default; |
| |
| void EventModelImpl::Initialize(OnModelInitializationFinished callback, |
| uint32_t current_day) { |
| store_->Load(base::BindOnce(&EventModelImpl::OnStoreLoaded, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| current_day)); |
| } |
| |
| bool EventModelImpl::IsReady() const { |
| return ready_; |
| } |
| |
| const Event* EventModelImpl::GetEvent(const std::string& event_name) const { |
| auto search = events_.find(event_name); |
| if (search == events_.end()) |
| return nullptr; |
| |
| return &search->second; |
| } |
| |
| uint32_t EventModelImpl::GetEventCount(const std::string& event_name, |
| uint32_t current_day, |
| uint32_t window_size) const { |
| uint32_t event_count = |
| GetEventCountOrSnooze(event_name, current_day, window_size, |
| /*is_snooze=*/false); |
| uint32_t snooze_count = |
| GetEventCountOrSnooze(event_name, current_day, window_size, |
| /*is_snooze=*/true); |
| return event_count - snooze_count; |
| } |
| |
| void EventModelImpl::IncrementEvent(const std::string& event_name, |
| uint32_t current_day) { |
| DCHECK(ready_); |
| |
| if (!storage_validator_->ShouldStore(event_name)) { |
| DVLOG(2) << "Not incrementing event " << event_name << " @ " << current_day; |
| return; |
| } |
| |
| DVLOG(2) << "Incrementing event " << event_name << " @ " << current_day; |
| |
| Event& event = GetNonConstEvent(event_name); |
| for (int i = 0; i < event.events_size(); ++i) { |
| Event_Count* event_count = event.mutable_events(i); |
| DCHECK(event_count->has_day()); |
| DCHECK(event_count->has_count()); |
| if (event_count->day() == current_day) { |
| event_count->set_count(event_count->count() + 1); |
| store_->WriteEvent(event); |
| return; |
| } |
| } |
| |
| // Day not found for event, adding new day with a count of 1. |
| Event_Count* event_count = event.add_events(); |
| event_count->set_day(current_day); |
| event_count->set_count(1u); |
| store_->WriteEvent(event); |
| } |
| |
| void EventModelImpl::IncrementSnooze(const std::string& event_name, |
| uint32_t current_day, |
| base::Time current_time) { |
| DCHECK(ready_); |
| |
| DVLOG(2) << "Incrementing snooze for event " << event_name << " on " |
| << current_day << " @ " << current_time; |
| |
| Event& event = GetNonConstEvent(event_name); |
| for (int i = 0; i < event.events_size(); ++i) { |
| Event_Count* event_count = event.mutable_events(i); |
| DCHECK(event_count->has_day()); |
| DCHECK(event_count->has_count()); |
| if (event_count->day() != current_day) |
| continue; |
| event_count->set_snooze_count( |
| event_count->has_snooze_count() ? event_count->snooze_count() + 1 : 1u); |
| } |
| event.set_last_snooze_time_us( |
| current_time.ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| store_->WriteEvent(event); |
| } |
| |
| void EventModelImpl::DismissSnooze(const std::string& event_name) { |
| DCHECK(ready_); |
| Event& event = GetNonConstEvent(event_name); |
| event.set_snooze_dismissed(true); |
| store_->WriteEvent(event); |
| } |
| |
| base::Time EventModelImpl::GetLastSnoozeTimestamp( |
| const std::string& event_name) const { |
| const Event* event = GetEvent(event_name); |
| |
| // If the Event object is not found, return. |
| if (!event) |
| return base::Time(); |
| |
| return base::Time::FromDeltaSinceWindowsEpoch( |
| base::Microseconds(event->last_snooze_time_us())); |
| } |
| |
| uint32_t EventModelImpl::GetSnoozeCount(const std::string& event_name, |
| uint32_t window, |
| uint32_t current_day) const { |
| return GetEventCountOrSnooze(event_name, current_day, window, |
| /*is_snooze=*/true); |
| } |
| |
| bool EventModelImpl::IsSnoozeDismissed(const std::string& event_name) const { |
| const Event* event = GetEvent(event_name); |
| return event ? event->snooze_dismissed() : false; |
| } |
| |
| void EventModelImpl::OnStoreLoaded(OnModelInitializationFinished callback, |
| uint32_t current_day, |
| bool success, |
| std::unique_ptr<std::vector<Event>> events) { |
| if (!success) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| for (auto& event : *events) { |
| DCHECK_NE("", event.name()); |
| |
| Event new_event; |
| for (const auto& event_count : event.events()) { |
| if (!storage_validator_->ShouldKeep(event.name(), event_count.day(), |
| current_day)) { |
| continue; |
| } |
| |
| Event_Count* new_event_count = new_event.add_events(); |
| new_event_count->set_day(event_count.day()); |
| new_event_count->set_count(event_count.count()); |
| new_event_count->set_snooze_count(event_count.snooze_count()); |
| } |
| |
| // Only keep Event object that have days with activity. |
| if (new_event.events_size() > 0) { |
| new_event.set_name(event.name()); |
| new_event.set_last_snooze_time_us(event.last_snooze_time_us()); |
| events_[event.name()] = new_event; |
| |
| // If the number of events is not the same, overwrite DB entry. |
| if (new_event.events_size() != event.events_size()) |
| store_->WriteEvent(new_event); |
| } else { |
| // If there are no more activity for an Event, delete the whole event. |
| store_->DeleteEvent(event.name()); |
| } |
| } |
| |
| ready_ = true; |
| std::move(callback).Run(true); |
| } |
| |
| int EventModelImpl::GetEventCountOrSnooze(const std::string& event_name, |
| int current_day, |
| int window, |
| bool is_snooze) const { |
| const Event* event = GetEvent(event_name); |
| |
| // If the Event object is not found, or if the window is 0 days, there will |
| // never be any events. |
| if (!event || window == 0u) |
| return 0; |
| |
| DCHECK(window >= 0); |
| |
| // A window of N=0: Nothing should be counted. |
| // A window of N=1: |current_day| should be counted. |
| // A window of N=2+: |current_day| plus |N-1| more days should be counted. |
| uint32_t oldest_accepted_day = current_day - window + 1; |
| |
| // Cap |oldest_accepted_day| to UNIX epoch. |
| if (window > current_day) |
| oldest_accepted_day = 0u; |
| |
| // Calculate the number of events within the window. |
| uint32_t count = 0; |
| for (const auto& event_day : event->events()) { |
| if (event_day.day() < oldest_accepted_day) |
| continue; |
| count += is_snooze ? event_day.snooze_count() : event_day.count(); |
| } |
| |
| return count; |
| } |
| |
| Event& EventModelImpl::GetNonConstEvent(const std::string& event_name) { |
| if (events_.find(event_name) == events_.end()) { |
| // Event does not exist yet, so create it. |
| events_[event_name].set_name(event_name); |
| store_->WriteEvent(events_[event_name]); |
| } |
| return events_[event_name]; |
| } |
| |
| } // namespace feature_engagement |