blob: c24ff33011a3462da635fcd36a0115b4aca45b78 [file] [log] [blame]
// Copyright 2020 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 <map>
#include <string>
#include <unordered_set>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/query_tiles/internal/stats.h"
#include "components/query_tiles/internal/tile_config.h"
#include "components/query_tiles/internal/tile_iterator.h"
#include "components/query_tiles/internal/tile_manager.h"
#include "components/query_tiles/internal/tile_utils.h"
#include "components/query_tiles/internal/trending_tile_handler.h"
#include "components/query_tiles/switches.h"
namespace query_tiles {
namespace {
// A special tile group for tile stats.
constexpr char kTileStatsGroup[] = "tile_stats";
class TileManagerImpl : public TileManager {
public:
TileManagerImpl(std::unique_ptr<TileStore> store,
base::Clock* clock,
const std::string& accept_languages)
: initialized_(false),
store_(std::move(store)),
clock_(clock),
accept_languages_(accept_languages) {}
private:
// TileManager implementation.
void Init(TileGroupStatusCallback callback) override {
store_->InitAndLoad(base::BindOnce(&TileManagerImpl::OnTileStoreInitialized,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void SaveTiles(std::unique_ptr<TileGroup> group,
TileGroupStatusCallback callback) override {
if (!initialized_) {
std::move(callback).Run(TileGroupStatus::kUninitialized);
return;
}
auto group_copy = *group;
store_->Update(group_copy.id, group_copy,
base::BindOnce(&TileManagerImpl::OnGroupSaved,
weak_ptr_factory_.GetWeakPtr(),
std::move(group), std::move(callback)));
}
void GetTiles(GetTilesCallback callback) override {
if (!tile_group_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<Tile>()));
return;
}
// First remove the inactive trending tiles.
RemoveIdleTrendingTiles();
// Now build the tiles to return. Don't filter the subtiles, as they are
// only used for UMA purpose now.
// TODO(qinmin): remove all subtiles before returning the result, as they
// are not used.
std::vector<Tile> tiles =
trending_tile_handler_.FilterExtraTrendingTiles(tile_group_->tiles);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(tiles)));
}
void GetTile(const std::string& tile_id, TileCallback callback) override {
// First remove the inactive trending tiles.
RemoveIdleTrendingTiles();
// Find the tile.
const Tile* result = nullptr;
if (tile_group_) {
TileIterator it(*tile_group_, TileIterator::kAllTiles);
while (it.HasNext()) {
const auto* tile = it.Next();
DCHECK(tile);
if (tile->id == tile_id) {
result = tile;
break;
}
}
}
auto result_tile = result ? absl::make_optional(*result) : absl::nullopt;
if (result_tile.has_value()) {
// Get the tiles to display, and convert the result vector.
// TODO(qinmin): make GetTile() return a vector of sub tiles, rather than
// the parent tile so we don't need the conversion below.
std::vector<Tile> sub_tiles =
trending_tile_handler_.FilterExtraTrendingTiles(
result_tile->sub_tiles);
if (!sub_tiles.empty()) {
std::vector<std::unique_ptr<Tile>> sub_tile_ptrs;
for (auto& tile : sub_tiles)
sub_tile_ptrs.emplace_back(std::make_unique<Tile>(std::move(tile)));
result_tile->sub_tiles = std::move(sub_tile_ptrs);
}
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(result_tile)));
}
TileGroupStatus PurgeDb() override {
if (!initialized_)
return TileGroupStatus::kUninitialized;
if (!tile_group_)
return TileGroupStatus::kNoTiles;
store_->Delete(tile_group_->id,
base::BindOnce(&TileManagerImpl::OnGroupDeleted,
weak_ptr_factory_.GetWeakPtr()));
tile_group_.reset();
return TileGroupStatus::kNoTiles;
}
void SetAcceptLanguagesForTesting(
const std::string& accept_languages) override {
accept_languages_ = accept_languages;
}
TileGroup* GetTileGroup() override {
return tile_group_ ? tile_group_.get() : nullptr;
}
void OnTileStoreInitialized(
TileGroupStatusCallback callback,
bool success,
std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) {
if (!success) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
TileGroupStatus::kFailureDbOperation));
return;
}
initialized_ = true;
PruneAndSelectGroup(std::move(callback), std::move(loaded_groups));
}
// Select the most recent unexpired group from |loaded_groups| with the
// correct locale, and delete other groups.
void PruneAndSelectGroup(
TileGroupStatusCallback callback,
std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) {
TileGroupStatus status = TileGroupStatus::kSuccess;
base::Time last_updated_time;
std::string selected_group_id;
for (const auto& pair : loaded_groups) {
DCHECK(!pair.first.empty()) << "Should not have empty tile group key.";
auto* group = pair.second.get();
if (!group)
continue;
if (pair.first == kTileStatsGroup)
continue;
if (ValidateLocale(group) && !IsGroupExpired(group) &&
(group->last_updated_ts > last_updated_time)) {
last_updated_time = group->last_updated_ts;
selected_group_id = pair.first;
}
}
// Moves the selected group into in memory holder.
if (!selected_group_id.empty()) {
tile_group_ = std::move(loaded_groups[selected_group_id]);
loaded_groups.erase(selected_group_id);
} else {
status = TileGroupStatus::kNoTiles;
}
// Keep the stats group in memory for tile score calculation.
if (loaded_groups.find(kTileStatsGroup) != loaded_groups.end() &&
base::FeatureList::IsEnabled(features::kQueryTilesLocalOrdering)) {
tile_stats_group_ = std::move(loaded_groups[kTileStatsGroup]);
// prevent the stats group from being deleted.
loaded_groups.erase(kTileStatsGroup);
if (tile_group_) {
SortTilesAndClearUnusedStats(&tile_group_->tiles,
&tile_stats_group_->tile_stats);
}
}
trending_tile_handler_.Reset();
// Deletes other groups.
for (const auto& group_to_delete : loaded_groups)
DeleteGroup(group_to_delete.first);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), status));
}
// Returns true if the group is expired.
bool IsGroupExpired(const TileGroup* group) const {
if (clock_->Now() >=
group->last_updated_ts + TileConfig::GetExpireDuration()) {
stats::RecordGroupPruned(stats::PrunedGroupReason::kExpired);
return true;
}
return false;
}
// Check whether |locale_| matches with that of the |group|.
bool ValidateLocale(const TileGroup* group) const {
if (!accept_languages_.empty() && !group->locale.empty()) {
// In case the primary language matches (en-GB vs en-IN), consider
// those are matching.
std::string group_primary =
group->locale.substr(0, group->locale.find("-"));
for (auto& lang :
base::SplitString(accept_languages_, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (lang.substr(0, lang.find("-")) == group_primary)
return true;
}
}
stats::RecordGroupPruned(stats::PrunedGroupReason::kInvalidLocale);
return false;
}
void OnGroupSaved(std::unique_ptr<TileGroup> group,
TileGroupStatusCallback callback,
bool success) {
if (!success) {
std::move(callback).Run(TileGroupStatus::kFailureDbOperation);
return;
}
// Only swap the in memory tile group when there is no existing tile group.
if (!tile_group_) {
tile_group_ = std::move(group);
trending_tile_handler_.Reset();
}
std::move(callback).Run(TileGroupStatus::kSuccess);
}
void DeleteGroup(const std::string& key) {
store_->Delete(key, base::BindOnce(&TileManagerImpl::OnGroupDeleted,
weak_ptr_factory_.GetWeakPtr()));
}
void OnGroupDeleted(bool success) {
// TODO(hesen): Record db operation metrics.
NOTIMPLEMENTED();
}
void OnTileClicked(const std::string& tile_id) override {
// If the tile stats haven't been created, create it here.
if (!tile_stats_group_) {
tile_stats_group_ = std::make_unique<TileGroup>();
tile_stats_group_->id = kTileStatsGroup;
}
tile_stats_group_->OnTileClicked(tile_id);
// It's fine if |tile_stats_group_| is not saved, so no callback needs to
// be passed to Update().
store_->Update(kTileStatsGroup, *tile_stats_group_, base::DoNothing());
trending_tile_handler_.OnTileClicked(tile_id);
}
void OnQuerySelected(const absl::optional<std::string>& parent_tile_id,
const std::u16string& query_text) override {
if (!tile_group_)
return;
// Find the parent tile first. If it cannot be found, that's fine as the
// old tile score will be used.
std::vector<std::unique_ptr<Tile>>* tiles = &tile_group_->tiles;
if (parent_tile_id) {
for (const auto& tile : tile_group_->tiles) {
if (tile->id == parent_tile_id.value()) {
tiles = &tile->sub_tiles;
break;
}
}
}
// Now check if a sub tile has the same query text.
for (const auto& tile : *tiles) {
if (query_text == base::UTF8ToUTF16(tile->query_text)) {
OnTileClicked(tile->id);
break;
}
}
}
void RemoveIdleTrendingTiles() {
if (!tile_group_)
return;
std::vector<std::string> tiles_to_remove =
trending_tile_handler_.GetInactiveTrendingTiles();
if (tiles_to_remove.empty())
return;
tile_group_->RemoveTiles(tiles_to_remove);
store_->Update(tile_group_->id, *tile_group_, base::DoNothing());
}
// Indicates if the db is fully initialized, rejects calls if not.
bool initialized_;
// Storage layer of query tiles.
std::unique_ptr<TileStore> store_;
// The tile group in-memory holder.
std::unique_ptr<TileGroup> tile_group_;
// The tile group that contains stats for ranking all tiles.
// TODO(qinmin): Having a separate TileGroup just for ranking the tiles
// seems weird, probably do it through a separate store or use PrefService.
std::unique_ptr<TileGroup> tile_stats_group_;
// Clock object.
base::Clock* clock_;
// Accept languages from the PrefService. Used to check if tiles stored are of
// the same language.
std::string accept_languages_;
// Object for managing trending tiles.
TrendingTileHandler trending_tile_handler_;
base::WeakPtrFactory<TileManagerImpl> weak_ptr_factory_{this};
};
} // namespace
TileManager::TileManager() = default;
std::unique_ptr<TileManager> TileManager::Create(
std::unique_ptr<TileStore> tile_store,
base::Clock* clock,
const std::string& locale) {
return std::make_unique<TileManagerImpl>(std::move(tile_store), clock,
locale);
}
} // namespace query_tiles