kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 1 | // Copyright 2018 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 | |
| 5 | #include "components/ntp_tiles/custom_links_manager_impl.h" |
| 6 | |
| 7 | #include <memory> |
| 8 | #include <string> |
| 9 | #include <utility> |
| 10 | |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 11 | #include "base/auto_reset.h" |
Sebastien Marchand | 53801a3 | 2019-01-25 16:26:11 | [diff] [blame] | 12 | #include "base/bind.h" |
Kristi Park | 01ca125 | 2019-05-10 22:39:05 | [diff] [blame] | 13 | #include "components/ntp_tiles/constants.h" |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 14 | #include "components/ntp_tiles/pref_names.h" |
| 15 | #include "components/pref_registry/pref_registry_syncable.h" |
| 16 | #include "components/prefs/pref_service.h" |
| 17 | |
| 18 | namespace ntp_tiles { |
| 19 | |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 20 | CustomLinksManagerImpl::CustomLinksManagerImpl( |
| 21 | PrefService* prefs, |
| 22 | history::HistoryService* history_service) |
Jeremy Roman | 5c341f6d | 2019-07-15 15:56:10 | [diff] [blame] | 23 | : prefs_(prefs), store_(prefs), history_service_observer_(this) { |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 24 | DCHECK(prefs); |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 25 | if (history_service) |
| 26 | history_service_observer_.Add(history_service); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 27 | if (IsInitialized()) |
| 28 | current_links_ = store_.RetrieveLinks(); |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 29 | |
| 30 | base::RepeatingClosure callback = |
| 31 | base::BindRepeating(&CustomLinksManagerImpl::OnPreferenceChanged, |
| 32 | weak_ptr_factory_.GetWeakPtr()); |
| 33 | pref_change_registrar_.Init(prefs_); |
| 34 | pref_change_registrar_.Add(prefs::kCustomLinksInitialized, callback); |
| 35 | pref_change_registrar_.Add(prefs::kCustomLinksList, callback); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 36 | } |
| 37 | |
| 38 | CustomLinksManagerImpl::~CustomLinksManagerImpl() = default; |
| 39 | |
| 40 | bool CustomLinksManagerImpl::Initialize(const NTPTilesVector& tiles) { |
| 41 | if (IsInitialized()) |
| 42 | return false; |
| 43 | |
| 44 | for (const NTPTile& tile : tiles) |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 45 | current_links_.emplace_back(Link{tile.url, tile.title, true}); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 46 | |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 47 | { |
| 48 | base::AutoReset<bool> auto_reset(&updating_preferences_, true); |
| 49 | prefs_->SetBoolean(prefs::kCustomLinksInitialized, true); |
| 50 | } |
| 51 | StoreLinks(); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 52 | return true; |
| 53 | } |
| 54 | |
| 55 | void CustomLinksManagerImpl::Uninitialize() { |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 56 | { |
| 57 | base::AutoReset<bool> auto_reset(&updating_preferences_, true); |
| 58 | prefs_->SetBoolean(prefs::kCustomLinksInitialized, false); |
| 59 | } |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 60 | ClearLinks(); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | bool CustomLinksManagerImpl::IsInitialized() const { |
| 64 | return prefs_->GetBoolean(prefs::kCustomLinksInitialized); |
| 65 | } |
| 66 | |
| 67 | const std::vector<CustomLinksManager::Link>& CustomLinksManagerImpl::GetLinks() |
| 68 | const { |
| 69 | return current_links_; |
| 70 | } |
| 71 | |
| 72 | bool CustomLinksManagerImpl::AddLink(const GURL& url, |
| 73 | const base::string16& title) { |
| 74 | if (!IsInitialized() || !url.is_valid() || |
Kristi Park | 01ca125 | 2019-05-10 22:39:05 | [diff] [blame] | 75 | current_links_.size() == ntp_tiles::kMaxNumCustomLinks) { |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 76 | return false; |
| 77 | } |
| 78 | |
| 79 | if (FindLinkWithUrl(url) != current_links_.end()) |
| 80 | return false; |
| 81 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 82 | previous_links_ = current_links_; |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 83 | current_links_.emplace_back(Link{url, title, false}); |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 84 | StoreLinks(); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 85 | return true; |
| 86 | } |
| 87 | |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 88 | bool CustomLinksManagerImpl::UpdateLink(const GURL& url, |
| 89 | const GURL& new_url, |
Kristi Park | 6d2d998 | 2018-10-25 00:16:58 | [diff] [blame] | 90 | const base::string16& new_title) { |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 91 | if (!IsInitialized() || !url.is_valid() || |
| 92 | (new_url.is_empty() && new_title.empty())) { |
| 93 | return false; |
| 94 | } |
| 95 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 96 | // Do not update if |new_url| is invalid or already exists in the list. |
| 97 | if (!new_url.is_empty() && |
| 98 | (!new_url.is_valid() || |
| 99 | FindLinkWithUrl(new_url) != current_links_.end())) { |
| 100 | return false; |
| 101 | } |
| 102 | |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 103 | auto it = FindLinkWithUrl(url); |
| 104 | if (it == current_links_.end()) |
| 105 | return false; |
| 106 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 107 | // At this point, we will be modifying at least one of the values. |
Kristi Park | 6d2d998 | 2018-10-25 00:16:58 | [diff] [blame] | 108 | previous_links_ = current_links_; |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 109 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 110 | if (!new_url.is_empty()) |
| 111 | it->url = new_url; |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 112 | if (!new_title.empty()) |
| 113 | it->title = new_title; |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 114 | it->is_most_visited = false; |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 115 | |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 116 | StoreLinks(); |
kristipark | 940dc20 | 2018-07-30 18:29:55 | [diff] [blame] | 117 | return true; |
| 118 | } |
| 119 | |
Kristi Park | 8c8047ec | 2018-10-31 22:17:29 | [diff] [blame] | 120 | bool CustomLinksManagerImpl::ReorderLink(const GURL& url, size_t new_pos) { |
| 121 | if (!IsInitialized() || !url.is_valid() || new_pos < 0 || |
| 122 | new_pos >= current_links_.size()) { |
| 123 | return false; |
| 124 | } |
| 125 | |
| 126 | auto curr_it = FindLinkWithUrl(url); |
| 127 | if (curr_it == current_links_.end()) |
| 128 | return false; |
| 129 | |
| 130 | auto new_it = current_links_.begin() + new_pos; |
| 131 | if (new_it == curr_it) |
| 132 | return false; |
| 133 | |
| 134 | previous_links_ = current_links_; |
| 135 | |
| 136 | // If the new position is to the left of the current position, left rotate the |
| 137 | // range [new_pos, curr_pos] until the link is first. |
| 138 | if (new_it < curr_it) |
| 139 | std::rotate(new_it, curr_it, curr_it + 1); |
| 140 | // If the new position is to the right, we only need to left rotate the range |
| 141 | // [curr_pos, new_pos] once so that the link is last. |
| 142 | else |
| 143 | std::rotate(curr_it, curr_it + 1, new_it + 1); |
| 144 | |
| 145 | StoreLinks(); |
| 146 | return true; |
| 147 | } |
| 148 | |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 149 | bool CustomLinksManagerImpl::DeleteLink(const GURL& url) { |
| 150 | if (!IsInitialized() || !url.is_valid()) |
| 151 | return false; |
| 152 | |
| 153 | auto it = FindLinkWithUrl(url); |
| 154 | if (it == current_links_.end()) |
| 155 | return false; |
| 156 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 157 | previous_links_ = current_links_; |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 158 | current_links_.erase(it); |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 159 | StoreLinks(); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 160 | return true; |
| 161 | } |
| 162 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 163 | bool CustomLinksManagerImpl::UndoAction() { |
| 164 | if (!IsInitialized() || !previous_links_.has_value()) |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 165 | return false; |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 166 | |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 167 | // Replace the current links with the previous state. |
| 168 | current_links_ = *previous_links_; |
| 169 | previous_links_ = base::nullopt; |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 170 | StoreLinks(); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 171 | return true; |
| 172 | } |
| 173 | |
| 174 | void CustomLinksManagerImpl::ClearLinks() { |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 175 | { |
| 176 | base::AutoReset<bool> auto_reset(&updating_preferences_, true); |
| 177 | store_.ClearLinks(); |
| 178 | } |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 179 | current_links_.clear(); |
kristipark | be00c656 | 2018-08-01 21:30:27 | [diff] [blame] | 180 | previous_links_ = base::nullopt; |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 181 | } |
| 182 | |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 183 | void CustomLinksManagerImpl::StoreLinks() { |
| 184 | base::AutoReset<bool> auto_reset(&updating_preferences_, true); |
| 185 | store_.StoreLinks(current_links_); |
| 186 | } |
| 187 | |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 188 | std::vector<CustomLinksManager::Link>::iterator |
| 189 | CustomLinksManagerImpl::FindLinkWithUrl(const GURL& url) { |
| 190 | return std::find_if(current_links_.begin(), current_links_.end(), |
| 191 | [&url](const Link& link) { return link.url == url; }); |
| 192 | } |
| 193 | |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 194 | std::unique_ptr<base::CallbackList<void()>::Subscription> |
| 195 | CustomLinksManagerImpl::RegisterCallbackForOnChanged( |
| 196 | base::RepeatingClosure callback) { |
| 197 | return callback_list_.Add(callback); |
| 198 | } |
| 199 | |
| 200 | // history::HistoryServiceObserver implementation. |
| 201 | void CustomLinksManagerImpl::OnURLsDeleted( |
| 202 | history::HistoryService* history_service, |
| 203 | const history::DeletionInfo& deletion_info) { |
| 204 | // We don't care about expired entries. |
| 205 | if (!IsInitialized() || deletion_info.is_from_expiration()) |
| 206 | return; |
| 207 | |
| 208 | size_t initial_size = current_links_.size(); |
| 209 | if (deletion_info.IsAllHistory()) { |
| 210 | base::EraseIf(current_links_, |
| 211 | [](auto& link) { return link.is_most_visited; }); |
| 212 | } else { |
| 213 | for (const history::URLRow& row : deletion_info.deleted_rows()) { |
| 214 | auto it = FindLinkWithUrl(row.url()); |
| 215 | if (it != current_links_.end() && it->is_most_visited) |
| 216 | current_links_.erase(it); |
| 217 | } |
| 218 | } |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 219 | StoreLinks(); |
kristipark | e79bcc3 | 2018-08-29 19:02:31 | [diff] [blame] | 220 | previous_links_ = base::nullopt; |
| 221 | |
| 222 | // Alert MostVisitedSites that some links have been deleted. |
| 223 | if (initial_size != current_links_.size()) |
| 224 | callback_list_.Notify(); |
| 225 | } |
| 226 | |
| 227 | void CustomLinksManagerImpl::HistoryServiceBeingDeleted( |
| 228 | history::HistoryService* history_service) { |
| 229 | history_service_observer_.RemoveAll(); |
| 230 | } |
| 231 | |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 232 | void CustomLinksManagerImpl::OnPreferenceChanged() { |
| 233 | if (updating_preferences_) |
| 234 | return; |
| 235 | |
| 236 | if (IsInitialized()) |
| 237 | current_links_ = store_.RetrieveLinks(); |
| 238 | else |
| 239 | current_links_.clear(); |
| 240 | previous_links_ = base::nullopt; |
| 241 | callback_list_.Notify(); |
| 242 | } |
| 243 | |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 244 | // static |
| 245 | void CustomLinksManagerImpl::RegisterProfilePrefs( |
| 246 | user_prefs::PrefRegistrySyncable* user_prefs) { |
Kristi Park | d7db601c | 2018-10-08 19:23:10 | [diff] [blame] | 247 | user_prefs->RegisterBooleanPref( |
| 248 | prefs::kCustomLinksInitialized, false, |
| 249 | user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
kristipark | c019977 | 2018-07-18 05:25:42 | [diff] [blame] | 250 | CustomLinksStore::RegisterProfilePrefs(user_prefs); |
| 251 | } |
| 252 | |
| 253 | } // namespace ntp_tiles |