tile_manager.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <map>
  5. #include <string>
  6. #include <unordered_set>
  7. #include <utility>
  8. #include "base/bind.h"
  9. #include "base/callback_helpers.h"
  10. #include "base/memory/weak_ptr.h"
  11. #include "base/strings/string_split.h"
  12. #include "base/strings/utf_string_conversions.h"
  13. #include "base/task/task_runner.h"
  14. #include "base/threading/thread_task_runner_handle.h"
  15. #include "base/time/time.h"
  16. #include "components/query_tiles/internal/stats.h"
  17. #include "components/query_tiles/internal/tile_config.h"
  18. #include "components/query_tiles/internal/tile_iterator.h"
  19. #include "components/query_tiles/internal/tile_manager.h"
  20. #include "components/query_tiles/internal/tile_utils.h"
  21. #include "components/query_tiles/internal/trending_tile_handler.h"
  22. #include "components/query_tiles/switches.h"
  23. namespace query_tiles {
  24. namespace {
  25. // A special tile group for tile stats.
  26. constexpr char kTileStatsGroup[] = "tile_stats";
  27. class TileManagerImpl : public TileManager {
  28. public:
  29. TileManagerImpl(std::unique_ptr<TileStore> store,
  30. const std::string& accept_languages)
  31. : initialized_(false),
  32. store_(std::move(store)),
  33. accept_languages_(accept_languages) {}
  34. private:
  35. // TileManager implementation.
  36. void Init(TileGroupStatusCallback callback) override {
  37. store_->InitAndLoad(base::BindOnce(&TileManagerImpl::OnTileStoreInitialized,
  38. weak_ptr_factory_.GetWeakPtr(),
  39. std::move(callback)));
  40. }
  41. void SaveTiles(std::unique_ptr<TileGroup> group,
  42. TileGroupStatusCallback callback) override {
  43. if (!initialized_) {
  44. std::move(callback).Run(TileGroupStatus::kUninitialized);
  45. return;
  46. }
  47. auto group_copy = *group;
  48. store_->Update(group_copy.id, group_copy,
  49. base::BindOnce(&TileManagerImpl::OnGroupSaved,
  50. weak_ptr_factory_.GetWeakPtr(),
  51. std::move(group), std::move(callback)));
  52. }
  53. void GetTiles(bool shuffle_tiles, GetTilesCallback callback) override {
  54. if (!tile_group_) {
  55. base::ThreadTaskRunnerHandle::Get()->PostTask(
  56. FROM_HERE, base::BindOnce(std::move(callback), std::vector<Tile>()));
  57. return;
  58. }
  59. // First remove the inactive trending tiles.
  60. RemoveIdleTrendingTiles();
  61. // Now build the tiles to return. Don't filter the subtiles, as they are
  62. // only used for UMA purpose now.
  63. // TODO(qinmin): remove all subtiles before returning the result, as they
  64. // are not used.
  65. std::vector<Tile> tiles =
  66. trending_tile_handler_.FilterExtraTrendingTiles(tile_group_->tiles);
  67. if (shuffle_tiles)
  68. ShuffleTiles(&tiles, TileShuffler());
  69. base::ThreadTaskRunnerHandle::Get()->PostTask(
  70. FROM_HERE, base::BindOnce(std::move(callback), std::move(tiles)));
  71. }
  72. void GetTile(const std::string& tile_id,
  73. bool shuffle_tiles,
  74. TileCallback callback) override {
  75. // First remove the inactive trending tiles.
  76. RemoveIdleTrendingTiles();
  77. // Find the tile.
  78. const Tile* result = nullptr;
  79. if (tile_group_) {
  80. TileIterator it(*tile_group_, TileIterator::kAllTiles);
  81. while (it.HasNext()) {
  82. const auto* tile = it.Next();
  83. DCHECK(tile);
  84. if (tile->id == tile_id) {
  85. result = tile;
  86. break;
  87. }
  88. }
  89. }
  90. auto result_tile = result ? absl::make_optional(*result) : absl::nullopt;
  91. if (result_tile.has_value()) {
  92. // Get the tiles to display, and convert the result vector.
  93. // TODO(qinmin): make GetTile() return a vector of sub tiles, rather than
  94. // the parent tile so we don't need the conversion below.
  95. std::vector<Tile> sub_tiles =
  96. trending_tile_handler_.FilterExtraTrendingTiles(
  97. result_tile->sub_tiles);
  98. if (!sub_tiles.empty()) {
  99. if (shuffle_tiles)
  100. ShuffleTiles(&sub_tiles, TileShuffler());
  101. std::vector<std::unique_ptr<Tile>> sub_tile_ptrs;
  102. for (auto& tile : sub_tiles)
  103. sub_tile_ptrs.emplace_back(std::make_unique<Tile>(std::move(tile)));
  104. result_tile->sub_tiles = std::move(sub_tile_ptrs);
  105. }
  106. }
  107. base::ThreadTaskRunnerHandle::Get()->PostTask(
  108. FROM_HERE, base::BindOnce(std::move(callback), std::move(result_tile)));
  109. }
  110. TileGroupStatus PurgeDb() override {
  111. if (!initialized_)
  112. return TileGroupStatus::kUninitialized;
  113. if (!tile_group_)
  114. return TileGroupStatus::kNoTiles;
  115. store_->Delete(tile_group_->id,
  116. base::BindOnce(&TileManagerImpl::OnGroupDeleted,
  117. weak_ptr_factory_.GetWeakPtr()));
  118. tile_group_.reset();
  119. return TileGroupStatus::kNoTiles;
  120. }
  121. void SetAcceptLanguagesForTesting(
  122. const std::string& accept_languages) override {
  123. accept_languages_ = accept_languages;
  124. }
  125. TileGroup* GetTileGroup() override {
  126. return tile_group_ ? tile_group_.get() : nullptr;
  127. }
  128. void OnTileStoreInitialized(
  129. TileGroupStatusCallback callback,
  130. bool success,
  131. std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) {
  132. if (!success) {
  133. base::ThreadTaskRunnerHandle::Get()->PostTask(
  134. FROM_HERE, base::BindOnce(std::move(callback),
  135. TileGroupStatus::kFailureDbOperation));
  136. return;
  137. }
  138. initialized_ = true;
  139. PruneAndSelectGroup(std::move(callback), std::move(loaded_groups));
  140. }
  141. // Select the most recent unexpired group from |loaded_groups| with the
  142. // correct locale, and delete other groups.
  143. void PruneAndSelectGroup(
  144. TileGroupStatusCallback callback,
  145. std::map<std::string, std::unique_ptr<TileGroup>> loaded_groups) {
  146. TileGroupStatus status = TileGroupStatus::kSuccess;
  147. base::Time last_updated_time;
  148. std::string selected_group_id;
  149. for (const auto& pair : loaded_groups) {
  150. DCHECK(!pair.first.empty()) << "Should not have empty tile group key.";
  151. auto* group = pair.second.get();
  152. if (!group)
  153. continue;
  154. if (pair.first == kTileStatsGroup)
  155. continue;
  156. if (ValidateLocale(group) && !IsGroupExpired(group) &&
  157. (group->last_updated_ts > last_updated_time)) {
  158. last_updated_time = group->last_updated_ts;
  159. selected_group_id = pair.first;
  160. }
  161. }
  162. // Moves the selected group into in memory holder.
  163. if (!selected_group_id.empty()) {
  164. tile_group_ = std::move(loaded_groups[selected_group_id]);
  165. loaded_groups.erase(selected_group_id);
  166. } else {
  167. status = TileGroupStatus::kNoTiles;
  168. }
  169. // Keep the stats group in memory for tile score calculation.
  170. if (loaded_groups.find(kTileStatsGroup) != loaded_groups.end()) {
  171. tile_stats_group_ = std::move(loaded_groups[kTileStatsGroup]);
  172. // prevent the stats group from being deleted.
  173. loaded_groups.erase(kTileStatsGroup);
  174. if (tile_group_) {
  175. SortTilesAndClearUnusedStats(&tile_group_->tiles,
  176. &tile_stats_group_->tile_stats);
  177. }
  178. }
  179. trending_tile_handler_.Reset();
  180. // Deletes other groups.
  181. for (const auto& group_to_delete : loaded_groups)
  182. DeleteGroup(group_to_delete.first);
  183. base::ThreadTaskRunnerHandle::Get()->PostTask(
  184. FROM_HERE, base::BindOnce(std::move(callback), status));
  185. }
  186. // Returns true if the group is expired.
  187. bool IsGroupExpired(const TileGroup* group) const {
  188. if (base::Time::Now() >=
  189. group->last_updated_ts + TileConfig::GetExpireDuration()) {
  190. stats::RecordGroupPruned(stats::PrunedGroupReason::kExpired);
  191. return true;
  192. }
  193. return false;
  194. }
  195. // Check whether |locale_| matches with that of the |group|.
  196. bool ValidateLocale(const TileGroup* group) const {
  197. if (!accept_languages_.empty() && !group->locale.empty()) {
  198. // In case the primary language matches (en-GB vs en-IN), consider
  199. // those are matching.
  200. std::string group_primary =
  201. group->locale.substr(0, group->locale.find("-"));
  202. for (auto& lang :
  203. base::SplitString(accept_languages_, ",", base::TRIM_WHITESPACE,
  204. base::SPLIT_WANT_NONEMPTY)) {
  205. if (lang.substr(0, lang.find("-")) == group_primary)
  206. return true;
  207. }
  208. }
  209. stats::RecordGroupPruned(stats::PrunedGroupReason::kInvalidLocale);
  210. return false;
  211. }
  212. void OnGroupSaved(std::unique_ptr<TileGroup> group,
  213. TileGroupStatusCallback callback,
  214. bool success) {
  215. if (!success) {
  216. std::move(callback).Run(TileGroupStatus::kFailureDbOperation);
  217. return;
  218. }
  219. // Only swap the in memory tile group when there is no existing tile group.
  220. if (!tile_group_) {
  221. tile_group_ = std::move(group);
  222. trending_tile_handler_.Reset();
  223. }
  224. std::move(callback).Run(TileGroupStatus::kSuccess);
  225. }
  226. void DeleteGroup(const std::string& key) {
  227. store_->Delete(key, base::BindOnce(&TileManagerImpl::OnGroupDeleted,
  228. weak_ptr_factory_.GetWeakPtr()));
  229. }
  230. void OnGroupDeleted(bool success) {
  231. // TODO(hesen): Record db operation metrics.
  232. NOTIMPLEMENTED();
  233. }
  234. void OnTileClicked(const std::string& tile_id) override {
  235. // If the tile stats haven't been created, create it here.
  236. if (!tile_stats_group_) {
  237. tile_stats_group_ = std::make_unique<TileGroup>();
  238. tile_stats_group_->id = kTileStatsGroup;
  239. }
  240. tile_stats_group_->OnTileClicked(tile_id);
  241. // It's fine if |tile_stats_group_| is not saved, so no callback needs to
  242. // be passed to Update().
  243. store_->Update(kTileStatsGroup, *tile_stats_group_, base::DoNothing());
  244. trending_tile_handler_.OnTileClicked(tile_id);
  245. }
  246. void OnQuerySelected(const absl::optional<std::string>& parent_tile_id,
  247. const std::u16string& query_text) override {
  248. if (!tile_group_)
  249. return;
  250. // Find the parent tile first. If it cannot be found, that's fine as the
  251. // old tile score will be used.
  252. std::vector<std::unique_ptr<Tile>>* tiles = &tile_group_->tiles;
  253. if (parent_tile_id) {
  254. for (const auto& tile : tile_group_->tiles) {
  255. if (tile->id == parent_tile_id.value()) {
  256. tiles = &tile->sub_tiles;
  257. break;
  258. }
  259. }
  260. }
  261. // Now check if a sub tile has the same query text.
  262. for (const auto& tile : *tiles) {
  263. if (query_text == base::UTF8ToUTF16(tile->query_text)) {
  264. OnTileClicked(tile->id);
  265. break;
  266. }
  267. }
  268. }
  269. void RemoveIdleTrendingTiles() {
  270. if (!tile_group_)
  271. return;
  272. std::vector<std::string> tiles_to_remove =
  273. trending_tile_handler_.GetTrendingTilesToRemove();
  274. if (tiles_to_remove.empty())
  275. return;
  276. tile_group_->RemoveTiles(tiles_to_remove);
  277. store_->Update(tile_group_->id, *tile_group_, base::DoNothing());
  278. }
  279. // Indicates if the db is fully initialized, rejects calls if not.
  280. bool initialized_;
  281. // Storage layer of query tiles.
  282. std::unique_ptr<TileStore> store_;
  283. // The tile group in-memory holder.
  284. std::unique_ptr<TileGroup> tile_group_;
  285. // The tile group that contains stats for ranking all tiles.
  286. // TODO(qinmin): Having a separate TileGroup just for ranking the tiles
  287. // seems weird, probably do it through a separate store or use PrefService.
  288. std::unique_ptr<TileGroup> tile_stats_group_;
  289. // Accept languages from the PrefService. Used to check if tiles stored are of
  290. // the same language.
  291. std::string accept_languages_;
  292. // Object for managing trending tiles.
  293. TrendingTileHandler trending_tile_handler_;
  294. base::WeakPtrFactory<TileManagerImpl> weak_ptr_factory_{this};
  295. };
  296. } // namespace
  297. TileManager::TileManager() = default;
  298. std::unique_ptr<TileManager> TileManager::Create(
  299. std::unique_ptr<TileStore> tile_store,
  300. const std::string& locale) {
  301. return std::make_unique<TileManagerImpl>(std::move(tile_store), locale);
  302. }
  303. } // namespace query_tiles