chromium/chrome/browser/ui/ash/holding_space/holding_space_persistence_delegate.cc

// Copyright 2020 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/ui/ash/holding_space/holding_space_persistence_delegate.h"

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/holding_space/holding_space_constants.h"
#include "ash/public/cpp/holding_space/holding_space_file.h"
#include "ash/public/cpp/holding_space/holding_space_image.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/holding_space/holding_space_progress.h"
#include "ash/public/cpp/holding_space/holding_space_util.h"
#include "base/containers/contains.h"
#include "base/ranges/algorithm.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/scoped_user_pref_update.h"

namespace ash {

namespace {

// Returns whether the item should be ignored by the holding space model. This
// returns true if the item is not supported in the current context, but may
// be otherwise supported. For example, returns true for ARC file system
// backed items in a secondary user profile.
bool ShouldIgnoreItem(Profile* profile, const HoldingSpaceItem* item) {
  return file_manager::util::GetAndroidFilesPath().IsParent(
             item->file().file_path) &&
         !ProfileHelper::IsPrimaryProfile(profile);
}

}  // namespace

// static
constexpr char HoldingSpacePersistenceDelegate::kPersistencePath[];

HoldingSpacePersistenceDelegate::HoldingSpacePersistenceDelegate(
    HoldingSpaceKeyedService* service,
    HoldingSpaceModel* model,
    ThumbnailLoader* thumbnail_loader,
    PersistenceRestoredCallback persistence_restored_callback)
    : HoldingSpaceKeyedServiceDelegate(service, model),
      thumbnail_loader_(thumbnail_loader),
      persistence_restored_callback_(std::move(persistence_restored_callback)) {
}

HoldingSpacePersistenceDelegate::~HoldingSpacePersistenceDelegate() = default;

// static
void HoldingSpacePersistenceDelegate::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterListPref(kPersistencePath);
}

void HoldingSpacePersistenceDelegate::Init() {
  // We expect that the associated profile is already ready when we are being
  // initialized. That being the case, we can immediately proceed to restore
  // the holding space model from persistence storage.
  RestoreModelFromPersistence();
}

void HoldingSpacePersistenceDelegate::OnHoldingSpaceItemsAdded(
    const std::vector<const HoldingSpaceItem*>& items) {
  if (is_restoring_persistence())
    return;

  // Write the new finalized `items` to persistent storage.
  ScopedListPrefUpdate update(profile()->GetPrefs(), kPersistencePath);
  for (const HoldingSpaceItem* item : items) {
    if (item->progress().IsComplete())
      update->Append(item->Serialize());
  }
}

void HoldingSpacePersistenceDelegate::OnHoldingSpaceItemsRemoved(
    const std::vector<const HoldingSpaceItem*>& items) {
  if (is_restoring_persistence())
    return;

  // Remove the `items` from persistent storage.
  ScopedListPrefUpdate update(profile()->GetPrefs(), kPersistencePath);
  update->EraseIf([&items](const base::Value& persisted_item) {
    const std::string& persisted_item_id =
        HoldingSpaceItem::DeserializeId(persisted_item.GetDict());
    return base::Contains(items, persisted_item_id, &HoldingSpaceItem::id);
  });
}

void HoldingSpacePersistenceDelegate::OnHoldingSpaceItemUpdated(
    const HoldingSpaceItem* item,
    const HoldingSpaceItemUpdatedFields& updated_fields) {
  if (is_restoring_persistence())
    return;

  // Only finalized items are persisted.
  if (!item->progress().IsComplete())
    return;

  // Attempt to find the finalized `item` in persistent storage.
  ScopedListPrefUpdate update(profile()->GetPrefs(), kPersistencePath);
  base::Value::List& list = update.Get();
  auto item_it = base::ranges::find(
      list, item->id(), [](const base::Value& persisted_item) {
        return HoldingSpaceItem::DeserializeId(persisted_item.GetDict());
      });

  // If the finalized `item` already exists in persistent storage, update it.
  if (item_it != list.end()) {
    *item_it = base::Value(item->Serialize());
    return;
  }

  // If the finalized `item` did not previously exist in persistent storage,
  // insert it at the appropriate index.
  item_it = list.begin();
  for (const auto& candidate_item : model()->items()) {
    if (candidate_item.get() == item) {
      list.Insert(item_it, base::Value(item->Serialize()));
      return;
    }
    if (candidate_item->progress().IsComplete())
      ++item_it;
  }

  // The finalized `item` should exist in the model and be handled above.
  NOTREACHED_IN_MIGRATION();
}

void HoldingSpacePersistenceDelegate::RestoreModelFromPersistence() {
  DCHECK(model()->items().empty());

  // Remove items from persistent storage that should not be restored to the
  // in-memory holding space model.
  MaybeRemoveItemsFromPersistence();

  const base::Value::List& persisted_holding_space_items =
      profile()->GetPrefs()->GetList(kPersistencePath);

  // If persistent storage is empty we can immediately notify the callback of
  // persistence restoration completion and quit early.
  std::vector<std::unique_ptr<HoldingSpaceItem>> restored_items;
  if (persisted_holding_space_items.empty()) {
    std::move(persistence_restored_callback_).Run(std::move(restored_items));
    return;
  }

  for (const auto& persisted_holding_space_item :
       persisted_holding_space_items) {
    std::unique_ptr<HoldingSpaceItem> holding_space_item =
        HoldingSpaceItem::Deserialize(
            persisted_holding_space_item.GetDict(),
            base::BindOnce(&holding_space_util::ResolveImage,
                           base::Unretained(thumbnail_loader_)));

    if (!ShouldIgnoreItem(profile(), holding_space_item.get())) {
      restored_items.push_back(std::move(holding_space_item));
    }
  }

  // Notify completion of persistence restoration.
  std::move(persistence_restored_callback_).Run(std::move(restored_items));
}

void HoldingSpacePersistenceDelegate::MaybeRemoveItemsFromPersistence() {
  CHECK(is_restoring_persistence());

  const auto known_types = holding_space_util::GetAllItemTypes();

  const bool remove_suggestion_items =
      !features::IsHoldingSpaceSuggestionsEnabled();

  ScopedListPrefUpdate update(profile()->GetPrefs(), kPersistencePath);
  update->EraseIf([&](const base::Value& persisted_item) {
    auto type = HoldingSpaceItem::DeserializeType(persisted_item.GetDict());

    // Remove items associated with unknown `type`s.
    if (!base::Contains(known_types, type)) {
      return true;
    }

    // Remove items associated with disabled features.
    if (remove_suggestion_items && HoldingSpaceItem::IsSuggestionType(type)) {
      return true;
    }

    return false;
  });
}

}  // namespace ash