blob: 83c2e43a74dc3793f837ef99613c62181e734d5f [file] [log] [blame] [edit]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/share/share_history.h"
#include "base/containers/flat_map.h"
#include "base/memory/ptr_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/share/proto/share_history_message.pb.h"
#include "components/leveldb_proto/public/proto_database_provider.h"
#include "content/public/browser/storage_partition.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_string.h"
#include "chrome/browser/profiles/profile.h"
// Must come after other includes, because FromJniType() uses Profile.
#include "chrome/browser/share/jni_headers/ShareHistoryBridge_jni.h"
using base::android::JavaParamRef;
#endif
namespace sharing {
namespace {
const char* const kShareHistoryFolder = "share_history";
// This is the key used for the single entry in the backing LevelDB. The fact
// that it is the same string as the above folder name is a coincidence; please
// do not fold these constants together.
const char* const kShareHistoryKey = "share_history";
constexpr auto kMaxHistoryAge = base::Days(90);
int TodaysDay() {
return (base::Time::Now() - base::Time::UnixEpoch()).InDays();
}
std::unique_ptr<ShareHistory::BackingDb> MakeDefaultDbForProfile(
Profile* profile) {
return profile->GetDefaultStoragePartition()
->GetProtoDatabaseProvider()
->GetDB<mojom::ShareHistory>(
leveldb_proto::ProtoDbType::SHARE_HISTORY_DATABASE,
profile->GetPath().AppendASCII(kShareHistoryFolder),
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
}
bool DayOverlapsTimeRange(base::Time day_start,
base::Time start,
base::Time end) {
const base::Time epoch = base::Time::UnixEpoch();
int day = (day_start - epoch).InDays();
int start_day = (start - epoch).InDays();
int end_day = (end - epoch).InDays();
return day >= start_day && day <= end_day;
}
} // namespace
// static
void ShareHistory::CreateForProfile(Profile* profile) {
CHECK(!profile->IsOffTheRecord());
auto instance = std::make_unique<ShareHistory>(profile);
profile->SetUserData(kShareHistoryKey, base::WrapUnique(instance.release()));
}
// static
ShareHistory* ShareHistory::Get(Profile* profile) {
if (profile->IsOffTheRecord())
return nullptr;
base::SupportsUserData::Data* instance =
profile->GetUserData(kShareHistoryKey);
if (!instance) {
CreateForProfile(profile);
instance = profile->GetUserData(kShareHistoryKey);
}
return static_cast<ShareHistory*>(instance);
}
ShareHistory::~ShareHistory() = default;
ShareHistory::ShareHistory() = default;
ShareHistory::ShareHistory(Profile* profile,
std::unique_ptr<BackingDb> backing_db)
: db_(backing_db ? std::move(backing_db)
: MakeDefaultDbForProfile(profile)) {
Init();
}
void ShareHistory::AddShareEntry(const std::string& component_name) {
if (!init_finished_) {
post_init_callbacks_.AddUnsafe(base::BindOnce(
&ShareHistory::AddShareEntry, base::Unretained(this), component_name));
return;
}
if (db_init_status_ != leveldb_proto::Enums::kOK)
return;
mojom::DayShareHistory* day_history = DayShareHistoryForToday();
mojom::TargetShareHistory* target_history =
TargetShareHistoryByName(day_history, component_name);
target_history->set_count(target_history->count() + 1);
FlushToBackingDb();
}
void ShareHistory::GetFlatShareHistory(GetFlatHistoryCallback callback,
int window) {
if (!init_finished_) {
post_init_callbacks_.AddUnsafe(base::BindOnce(
&ShareHistory::GetFlatShareHistory, weak_factory_.GetWeakPtr(),
std::move(callback), window));
return;
}
if (db_init_status_ != leveldb_proto::Enums::kOK) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<Target>()));
return;
}
base::flat_map<std::string, int> counts;
int today = TodaysDay();
for (const auto& day : history_.day_histories()) {
if (window != -1 && today - day.day() > window)
continue;
for (const auto& target : day.target_histories()) {
counts[target.target().component_name()] += target.count();
}
}
std::vector<Target> result;
for (const auto& count : counts) {
Target t;
t.component_name = count.first;
t.count = count.second;
result.push_back(t);
}
std::sort(result.begin(), result.end(),
[](const Target& a, const Target& b) { return a.count > b.count; });
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
}
void ShareHistory::Clear(const base::Time& start, const base::Time& end) {
google::protobuf::RepeatedPtrField<mojom::DayShareHistory> histories_to_keep;
for (const auto& day : history_.day_histories()) {
base::Time day_start = base::Time::UnixEpoch() + base::Days(day.day());
if (!DayOverlapsTimeRange(day_start, start, end)) {
mojom::DayShareHistory this_day;
this_day.CopyFrom(day);
histories_to_keep.Add(std::move(this_day));
}
}
history_.mutable_day_histories()->Swap(&histories_to_keep);
FlushToBackingDb();
}
void ShareHistory::Init() {
db_->Init(
base::BindOnce(&ShareHistory::OnInitDone, weak_factory_.GetWeakPtr()));
}
void ShareHistory::OnInitDone(leveldb_proto::Enums::InitStatus status) {
db_init_status_ = status;
if (status != leveldb_proto::Enums::kOK) {
// If the LevelDB initialization failed, follow the same state transitions
// as in the happy case, but without going through LevelDB; i.e., act as
// though the initial read failed, instead of the LevelDB initialization, so
// that control always ends up in OnInitialReadDone.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ShareHistory::OnInitialReadDone,
weak_factory_.GetWeakPtr(), false,
std::make_unique<mojom::ShareHistory>()));
return;
}
db_->GetEntry(kShareHistoryKey,
base::BindOnce(&ShareHistory::OnInitialReadDone,
weak_factory_.GetWeakPtr()));
}
void ShareHistory::OnInitialReadDone(
bool ok,
std::unique_ptr<mojom::ShareHistory> history) {
if (ok && history)
history_ = *history;
init_finished_ = true;
post_init_callbacks_.Notify();
Clear(base::Time(), base::Time::Now() - kMaxHistoryAge);
}
void ShareHistory::FlushToBackingDb() {
auto keyvals = std::make_unique<BackingDb::KeyEntryVector>();
keyvals->push_back({kShareHistoryKey, history_});
db_->UpdateEntries(std::move(keyvals),
std::make_unique<std::vector<std::string>>(),
base::DoNothing());
}
mojom::DayShareHistory* ShareHistory::DayShareHistoryForToday() {
int today = TodaysDay();
for (auto& day : *history_.mutable_day_histories()) {
if (day.day() == today)
return &day;
}
mojom::DayShareHistory* day = history_.mutable_day_histories()->Add();
day->set_day(today);
return day;
}
mojom::TargetShareHistory* ShareHistory::TargetShareHistoryByName(
mojom::DayShareHistory* history,
const std::string& target_name) {
for (auto& target : *history->mutable_target_histories()) {
if (target.target().component_name() == target_name)
return &target;
}
mojom::TargetShareHistory* target =
history->mutable_target_histories()->Add();
target->mutable_target()->set_component_name(target_name);
return target;
}
} // namespace sharing
#if BUILDFLAG(IS_ANDROID)
void JNI_ShareHistoryBridge_AddShareEntry(JNIEnv* env,
Profile* profile,
const JavaParamRef<jstring>& name) {
auto* instance = sharing::ShareHistory::Get(profile);
if (instance)
instance->AddShareEntry(base::android::ConvertJavaStringToUTF8(env, name));
}
void JNI_ShareHistoryBridge_Clear(JNIEnv* env, Profile* profile) {
auto* instance = sharing::ShareHistory::Get(profile);
if (instance)
instance->Clear();
}
#endif