// 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/ash/app_list/chrome_app_list_item_manager.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "chrome/browser/ash/app_list/chrome_app_list_item.h"
ChromeAppListItemManager::ChromeAppListItemManager() = default;
ChromeAppListItemManager::~ChromeAppListItemManager() = default;
ChromeAppListItem* ChromeAppListItemManager::FindItem(const std::string& id) {
auto iter = items_.find(id);
return iter != items_.end() ? iter->second.get() : nullptr;
}
ChromeAppListItem* ChromeAppListItemManager::AddChromeItem(
std::unique_ptr<ChromeAppListItem> app_item) {
ChromeAppListItem* item = app_item.get();
items_[item->id()] = std::move(app_item);
if (item->is_folder()) {
folder_item_mappings_.emplace(
item->id(),
std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>());
} else if (!item->folder_id().empty()) {
AddChildItemToFolderItemMapping(item, item->folder_id());
}
return item;
}
void ChromeAppListItemManager::UpdateChromeItem(
const std::string& id,
std::unique_ptr<ash::AppListItemMetadata> data) {
ChromeAppListItem* item = FindItem(id);
// The old metadata is destroyed after setting the new data. Therefore copy
// the old data.
const std::string old_folder = item->folder_id();
const syncer::StringOrdinal old_position = item->position();
item->SetMetadata(std::move(data));
const std::string& new_folder = item->folder_id();
if (old_folder != new_folder) {
if (!old_folder.empty())
RemoveChildFromFolderItemMapping(item, old_folder);
if (!new_folder.empty())
AddChildItemToFolderItemMapping(item, new_folder);
// The new position is taken into consideration when adding an item to
// folder so nothing to do.
return;
}
const syncer::StringOrdinal& new_positon = item->position();
if (old_position.IsValid() && new_positon.IsValid() &&
old_position.Equals(new_positon)) {
return;
}
if (new_folder.empty())
return;
// Remove `item` from the sorted children list then add it back to ensure that
// `item` is placed in the sorted list correctly after position update.
// TODO(crbug.com/40203095): if `new_position` is always valid, clean
// this code by using a function that moves an item in the sorted list.
DCHECK(old_position.IsValid());
RemoveChildFromFolderItemMapping(item, new_folder);
AddChildItemToFolderItemMapping(item, new_folder);
}
void ChromeAppListItemManager::RemoveChromeItem(const std::string& id) {
auto* item = FindItem(id);
DCHECK(item);
if (item->is_folder()) {
auto iter = folder_item_mappings_.find(id);
DCHECK(iter != folder_item_mappings_.end());
DCHECK(iter->second.empty());
folder_item_mappings_.erase(iter);
} else if (!item->folder_id().empty()) {
RemoveChildFromFolderItemMapping(item, item->folder_id());
}
items_.erase(id);
}
size_t ChromeAppListItemManager::ItemCount() const {
return items_.size();
}
int ChromeAppListItemManager::BadgedItemCount() const {
size_t count = 0u;
for (const auto& key_val : items_) {
if (key_val.second->IsBadged())
++count;
}
return count;
}
std::vector<ChromeAppListItem*> ChromeAppListItemManager::GetTopLevelItems()
const {
std::vector<ChromeAppListItem*> top_level_items;
for (auto& entry : items_) {
ChromeAppListItem* item = entry.second.get();
DCHECK(item->position().IsValid())
<< "Item with invalid position: id=" << item->id()
<< ", name=" << item->name() << ", is_folder=" << item->is_folder();
if (item->folder_id().empty() && item->position().IsValid())
top_level_items.emplace_back(item);
}
return top_level_items;
}
syncer::StringOrdinal ChromeAppListItemManager::CreateChromePositionOnLast()
const {
syncer::StringOrdinal last_known_position;
for (auto& it : items_) {
if (!last_known_position.IsValid() ||
(it.second->position().IsValid() &&
it.second->position().GreaterThan(last_known_position))) {
last_known_position = it.second->position();
}
}
return last_known_position.IsValid()
? last_known_position.CreateAfter()
: syncer::StringOrdinal::CreateInitialOrdinal();
}
ChromeAppListItem* ChromeAppListItemManager::FindLastChildInFolder(
const std::string& folder_id) {
auto iter = folder_item_mappings_.find(folder_id);
DCHECK(iter != folder_item_mappings_.end());
const std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>&
sorted_children = iter->second;
if (sorted_children.empty())
return nullptr;
return sorted_children.back();
}
void ChromeAppListItemManager::AddChildItemToFolderItemMapping(
ChromeAppListItem* child_item,
const std::string& dst_folder) {
DCHECK(!dst_folder.empty());
// Find the target folder's children.
auto iter = folder_item_mappings_.find(dst_folder);
DCHECK(iter != folder_item_mappings_.end());
std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>*
sorted_children_ptr = &iter->second;
EnsureChildItemValidPosition(child_item, *sorted_children_ptr);
size_t target_index = GetItemSortOrderIndex(child_item, *sorted_children_ptr);
sorted_children_ptr->insert(sorted_children_ptr->begin() + target_index,
child_item);
}
void ChromeAppListItemManager::RemoveChildFromFolderItemMapping(
ChromeAppListItem* child_item,
const std::string& src_folder) {
DCHECK(!src_folder.empty());
// Find the source folder's children.
auto folder_item_mappings_iter = folder_item_mappings_.find(src_folder);
DCHECK(folder_item_mappings_iter != folder_item_mappings_.end());
std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>*
sorted_children_ptr = &folder_item_mappings_iter->second;
auto children_array_iter =
base::ranges::find(*sorted_children_ptr, child_item);
DCHECK(children_array_iter != sorted_children_ptr->cend());
// Delete `child_item` from `src_folder`'s children list.
sorted_children_ptr->erase(children_array_iter);
}
void ChromeAppListItemManager::EnsureChildItemValidPosition(
ChromeAppListItem* child_item,
const std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>&
sorted_children) {
syncer::StringOrdinal position = child_item->position();
if (position.IsValid())
return;
size_t nitems = sorted_children.size();
if (nitems == 0) {
position = syncer::StringOrdinal::CreateInitialOrdinal();
} else {
position = sorted_children.back()->position().CreateAfter();
}
child_item->SetChromePosition(position);
}
size_t ChromeAppListItemManager::GetItemSortOrderIndex(
ChromeAppListItem* child_item,
const std::vector<raw_ptr<ChromeAppListItem, VectorExperimental>>&
sorted_children) {
const syncer::StringOrdinal& position = child_item->position();
const std::string& id = child_item->id();
DCHECK(position.IsValid());
for (size_t index = 0; index < sorted_children.size(); ++index) {
if (position.LessThan(sorted_children[index]->position()) ||
(position.Equals(sorted_children[index]->position()) &&
(id < sorted_children[index]->id()))) {
return index;
}
}
return sorted_children.size();
}