chromium/ios/chrome/browser/bookmarks/model/bookmarks_utils.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ios/chrome/browser/bookmarks/model/bookmarks_utils.h"

#include "base/check.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_functions.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
#include "ios/chrome/browser/bookmarks/model/bookmark_storage_type.h"
#include "ios/chrome/browser/shared/model/prefs/pref_names.h"

using bookmarks::BookmarkNode;

namespace {

void LogDefaultBookmarkFolderOutcome(
    DefaultBookmarkFolderOutcomeForMetrics value) {
  base::UmaHistogramEnumeration("IOS.Bookmarks.DefaultBookmarkFolderOutcome",
                                value);
}

}  // namespace

const int64_t kLastUsedBookmarkFolderNone = -1;

std::vector<const bookmarks::BookmarkNode*> PrimaryPermanentNodes(
    const bookmarks::BookmarkModel* model,
    BookmarkStorageType type) {
  CHECK(model);
  CHECK(model->loaded());

  switch (type) {
    case BookmarkStorageType::kLocalOrSyncable:
      return {model->mobile_node(), model->bookmark_bar_node(),
              model->other_node()};
    case BookmarkStorageType::kAccount: {
      std::vector<const bookmarks::BookmarkNode*> nodes;
      // Normally either all account permanent nodes exists or none does, but
      // during transitional states (i.e. during creation or removal of account
      // permanent nodes) it is also possible that a subset exists.
      for (const bookmarks::BookmarkNode* node :
           {model->account_mobile_node(), model->account_bookmark_bar_node(),
            model->account_other_node()}) {
        if (node) {
          nodes.push_back(node);
        }
      }
      return nodes;
    }
  }
  NOTREACHED();
}

bool IsLastUsedBookmarkFolderSet(PrefService* prefs) {
  return prefs->GetInt64(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks) ==
         kLastUsedBookmarkFolderNone;
}

void ResetLastUsedBookmarkFolder(PrefService* prefs) {
  prefs->ClearPref(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks);
  prefs->ClearPref(prefs::kIosBookmarkLastUsedStorageReceivingBookmarks);
}

void SetLastUsedBookmarkFolder(PrefService* prefs,
                               const bookmarks::BookmarkNode* folder,
                               BookmarkStorageType type) {
  CHECK(folder);
  CHECK(folder->is_folder()) << "node type: " << folder->type()
                             << ", storage type: " << static_cast<int>(type);
  prefs->SetInt64(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks,
                  folder->id());
  prefs->SetInteger(prefs::kIosBookmarkLastUsedStorageReceivingBookmarks,
                    static_cast<int>(type));
}

const bookmarks::BookmarkNode* GetDefaultBookmarkFolder(
    PrefService* prefs,
    const bookmarks::BookmarkModel* bookmark_model) {
  int64_t node_id =
      prefs->GetInt64(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks);

  if (node_id == kLastUsedBookmarkFolderNone) {
    LogDefaultBookmarkFolderOutcome(
        DefaultBookmarkFolderOutcomeForMetrics::kUnset);
  } else {
    BookmarkStorageType type =
        static_cast<BookmarkStorageType>(prefs->GetInteger(
            prefs::kIosBookmarkLastUsedStorageReceivingBookmarks));

    const BookmarkNode* result =
        bookmarks::GetBookmarkNodeByID(bookmark_model, node_id);
    if (result && result->is_folder()) {
      // Make sure the bookmark node is a folder. See crbug.com/1450146.
      LogDefaultBookmarkFolderOutcome(
          bookmark_model->IsLocalOnlyNode(*result)
              ? DefaultBookmarkFolderOutcomeForMetrics::kExistingLocalFolderSet
              : DefaultBookmarkFolderOutcomeForMetrics::
                    kExistingAccountFolderSet);
      return result;
    } else {
      LogDefaultBookmarkFolderOutcome(
          (type == BookmarkStorageType::kLocalOrSyncable)
              ? DefaultBookmarkFolderOutcomeForMetrics::kMissingLocalFolderSet
              : DefaultBookmarkFolderOutcomeForMetrics::
                    kMissingAccountFolderSet);
    }
  }

  // Either preferences is not set, or refers to a non-existing folder.
  return (bookmark_model->account_mobile_node() != nullptr)
             ? bookmark_model->account_mobile_node()
             : bookmark_model->mobile_node();
}

void MigrateLastUsedBookmarkFolderUponLocalIdsReassigned(
    PrefService* prefs,
    const std::multimap<int64_t, int64_t>&
        local_or_syncable_reassigned_ids_per_old_id) {
  const int64_t node_id_in_prefs =
      prefs->GetInt64(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks);

  if (node_id_in_prefs == kLastUsedBookmarkFolderNone) {
    return;
  }

  const BookmarkStorageType type = static_cast<BookmarkStorageType>(
      prefs->GetInteger(prefs::kIosBookmarkLastUsedStorageReceivingBookmarks));
  if (type != BookmarkStorageType::kLocalOrSyncable) {
    // Account bookmarks don't get their IDs reassigned as a result of the
    // migration covered here (the adoption of a single BookmarkModel on iOS,
    // whereas previously this client may have used two of them).
    return;
  }

  const size_t match_count =
      local_or_syncable_reassigned_ids_per_old_id.count(node_id_in_prefs);
  if (match_count == 0) {
    // ID not reassigned; nothing to do.
    return;
  }

  if (match_count != 1) {
    // ID reassignment ambiguous: this should be very rare and hence not
    // supported.
    return;
  }

  const int64_t new_node_id =
      local_or_syncable_reassigned_ids_per_old_id.find(node_id_in_prefs)
          ->second;
  prefs->SetInt64(prefs::kIosBookmarkLastUsedFolderReceivingBookmarks,
                  new_node_id);
}