blob: 5f9e94c2a90103e76bbd491733abac52b6c2af82 [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 "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
#include <algorithm>
#include <sstream>
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
#include "chromeos/components/local_search_service/local_search_service_sync.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/base/l10n/l10n_util.h"
namespace chromeos {
namespace settings {
namespace {
std::vector<int> GetMessageIds(const SearchConcept* concept) {
// Start with only the canonical ID.
std::vector<int> alt_tag_message_ids{concept->canonical_message_id};
// Add alternate IDs, if they exist.
for (size_t i = 0; i < SearchConcept::kMaxAltTagsPerConcept; ++i) {
int curr_alt_tag_message_id = concept->alt_tag_ids[i];
if (curr_alt_tag_message_id == SearchConcept::kAltTagEnd)
break;
alt_tag_message_ids.push_back(curr_alt_tag_message_id);
}
return alt_tag_message_ids;
}
} // namespace
SearchTagRegistry::ScopedTagUpdater::ScopedTagUpdater(
SearchTagRegistry* registry)
: registry_(registry) {}
SearchTagRegistry::ScopedTagUpdater::ScopedTagUpdater(ScopedTagUpdater&&) =
default;
SearchTagRegistry::ScopedTagUpdater::~ScopedTagUpdater() {
std::vector<const SearchConcept*> pending_adds;
std::vector<const SearchConcept*> pending_removals;
for (const auto& map_entry : pending_updates_) {
const std::string& result_id = map_entry.first;
const SearchConcept* concept = map_entry.second.first;
bool is_pending_add = map_entry.second.second;
// If tag metadata is present for this tag, it has already been added and is
// present in LocalSearchServiceSync.
bool is_concept_already_added =
registry_->GetTagMetadata(result_id) != nullptr;
// Only add concepts which are intended to be added and have not yet been
// added; only remove concepts which are intended to be removed and have
// already been added.
if (is_pending_add && !is_concept_already_added)
pending_adds.push_back(concept);
if (!is_pending_add && is_concept_already_added)
pending_removals.push_back(concept);
}
if (!pending_adds.empty())
registry_->AddSearchTags(pending_adds);
if (!pending_removals.empty())
registry_->RemoveSearchTags(pending_removals);
}
void SearchTagRegistry::ScopedTagUpdater::AddSearchTags(
const std::vector<SearchConcept>& search_tags) {
ProcessPendingSearchTags(search_tags, /*is_pending_add=*/true);
}
void SearchTagRegistry::ScopedTagUpdater::RemoveSearchTags(
const std::vector<SearchConcept>& search_tags) {
ProcessPendingSearchTags(search_tags, /*is_pending_add=*/false);
}
void SearchTagRegistry::ScopedTagUpdater::ProcessPendingSearchTags(
const std::vector<SearchConcept>& search_tags,
bool is_pending_add) {
for (const auto& concept : search_tags) {
std::string result_id = ToResultId(concept);
auto it = pending_updates_.find(result_id);
if (it == pending_updates_.end()) {
pending_updates_.emplace(std::piecewise_construct,
std::forward_as_tuple(result_id),
std::forward_as_tuple(&concept, is_pending_add));
} else {
it->second.second = is_pending_add;
}
}
}
SearchTagRegistry::SearchTagRegistry(
local_search_service::LocalSearchServiceSync* local_search_service)
: index_(local_search_service->GetIndexSync(
local_search_service::IndexId::kCrosSettings,
local_search_service::Backend::kLinearMap,
g_browser_process ? g_browser_process->local_state() : nullptr)) {}
SearchTagRegistry::~SearchTagRegistry() = default;
void SearchTagRegistry::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void SearchTagRegistry::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
SearchTagRegistry::ScopedTagUpdater SearchTagRegistry::StartUpdate() {
return ScopedTagUpdater(this);
}
void SearchTagRegistry::AddSearchTags(
const std::vector<const SearchConcept*>& search_tags) {
index_->AddOrUpdateSync(ConceptVectorToDataVector(search_tags));
// Add each concept to the map. Note that it is safe to take the address of
// each concept because all concepts are allocated via static
// base::NoDestructor objects in the Get*SearchConcepts() helper functions.
for (const auto* concept : search_tags)
result_id_to_metadata_list_map_[ToResultId(*concept)] = concept;
NotifyRegistryUpdated();
}
void SearchTagRegistry::RemoveSearchTags(
const std::vector<const SearchConcept*>& search_tags) {
std::vector<std::string> data_ids;
for (const auto* concept : search_tags) {
std::string result_id = ToResultId(*concept);
result_id_to_metadata_list_map_.erase(result_id);
data_ids.push_back(std::move(result_id));
}
index_->DeleteSync(data_ids);
NotifyRegistryUpdated();
}
const SearchConcept* SearchTagRegistry::GetTagMetadata(
const std::string& result_id) const {
const auto it = result_id_to_metadata_list_map_.find(result_id);
if (it == result_id_to_metadata_list_map_.end())
return nullptr;
return it->second;
}
// static
std::string SearchTagRegistry::ToResultId(const SearchConcept& concept) {
std::stringstream ss;
switch (concept.type) {
case mojom::SearchResultType::kSection:
ss << concept.id.section;
break;
case mojom::SearchResultType::kSubpage:
ss << concept.id.subpage;
break;
case mojom::SearchResultType::kSetting:
ss << concept.id.setting;
break;
}
ss << "," << concept.canonical_message_id;
return ss.str();
}
std::vector<local_search_service::Data>
SearchTagRegistry::ConceptVectorToDataVector(
const std::vector<const SearchConcept*>& search_tags) {
std::vector<local_search_service::Data> data_list;
for (const auto* concept : search_tags) {
// Create a list of Content objects, which use the stringified version of
// message IDs as identifiers.
std::vector<local_search_service::Content> content_list;
for (int message_id : GetMessageIds(concept)) {
content_list.emplace_back(
/*id=*/base::NumberToString(message_id),
/*content=*/l10n_util::GetStringUTF16(message_id));
}
// Compute an identifier for this result; the same ID format it used in
// GetTagMetadata().
data_list.emplace_back(ToResultId(*concept), std::move(content_list));
}
return data_list;
}
void SearchTagRegistry::NotifyRegistryUpdated() {
for (auto& observer : observer_list_)
observer.OnRegistryUpdated();
}
} // namespace settings
} // namespace chromeos