blob: 1783dc361a4c26c27198854acef27c56a1337f2d [file] [log] [blame]
kristiparkc0199772018-07-18 05:25:421// 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 Parkd7db601c2018-10-08 19:23:1011#include "base/auto_reset.h"
Sebastien Marchand53801a32019-01-25 16:26:1112#include "base/bind.h"
Kristi Park01ca1252019-05-10 22:39:0513#include "components/ntp_tiles/constants.h"
kristiparkc0199772018-07-18 05:25:4214#include "components/ntp_tiles/pref_names.h"
15#include "components/pref_registry/pref_registry_syncable.h"
16#include "components/prefs/pref_service.h"
17
18namespace ntp_tiles {
19
kristiparke79bcc32018-08-29 19:02:3120CustomLinksManagerImpl::CustomLinksManagerImpl(
21 PrefService* prefs,
22 history::HistoryService* history_service)
Jeremy Roman5c341f6d2019-07-15 15:56:1023 : prefs_(prefs), store_(prefs), history_service_observer_(this) {
kristiparkc0199772018-07-18 05:25:4224 DCHECK(prefs);
kristiparke79bcc32018-08-29 19:02:3125 if (history_service)
26 history_service_observer_.Add(history_service);
kristiparkc0199772018-07-18 05:25:4227 if (IsInitialized())
28 current_links_ = store_.RetrieveLinks();
Kristi Parkd7db601c2018-10-08 19:23:1029
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);
kristiparkc0199772018-07-18 05:25:4236}
37
38CustomLinksManagerImpl::~CustomLinksManagerImpl() = default;
39
40bool CustomLinksManagerImpl::Initialize(const NTPTilesVector& tiles) {
41 if (IsInitialized())
42 return false;
43
44 for (const NTPTile& tile : tiles)
kristiparke79bcc32018-08-29 19:02:3145 current_links_.emplace_back(Link{tile.url, tile.title, true});
kristiparkc0199772018-07-18 05:25:4246
Kristi Parkd7db601c2018-10-08 19:23:1047 {
48 base::AutoReset<bool> auto_reset(&updating_preferences_, true);
49 prefs_->SetBoolean(prefs::kCustomLinksInitialized, true);
50 }
51 StoreLinks();
kristiparkc0199772018-07-18 05:25:4252 return true;
53}
54
55void CustomLinksManagerImpl::Uninitialize() {
Kristi Parkd7db601c2018-10-08 19:23:1056 {
57 base::AutoReset<bool> auto_reset(&updating_preferences_, true);
58 prefs_->SetBoolean(prefs::kCustomLinksInitialized, false);
59 }
kristiparkc0199772018-07-18 05:25:4260 ClearLinks();
kristiparkc0199772018-07-18 05:25:4261}
62
63bool CustomLinksManagerImpl::IsInitialized() const {
64 return prefs_->GetBoolean(prefs::kCustomLinksInitialized);
65}
66
67const std::vector<CustomLinksManager::Link>& CustomLinksManagerImpl::GetLinks()
68 const {
69 return current_links_;
70}
71
72bool CustomLinksManagerImpl::AddLink(const GURL& url,
73 const base::string16& title) {
74 if (!IsInitialized() || !url.is_valid() ||
Kristi Park01ca1252019-05-10 22:39:0575 current_links_.size() == ntp_tiles::kMaxNumCustomLinks) {
kristiparkc0199772018-07-18 05:25:4276 return false;
77 }
78
79 if (FindLinkWithUrl(url) != current_links_.end())
80 return false;
81
kristiparkbe00c6562018-08-01 21:30:2782 previous_links_ = current_links_;
kristiparke79bcc32018-08-29 19:02:3183 current_links_.emplace_back(Link{url, title, false});
Kristi Parkd7db601c2018-10-08 19:23:1084 StoreLinks();
kristiparkc0199772018-07-18 05:25:4285 return true;
86}
87
kristipark940dc202018-07-30 18:29:5588bool CustomLinksManagerImpl::UpdateLink(const GURL& url,
89 const GURL& new_url,
Kristi Park6d2d9982018-10-25 00:16:5890 const base::string16& new_title) {
kristipark940dc202018-07-30 18:29:5591 if (!IsInitialized() || !url.is_valid() ||
92 (new_url.is_empty() && new_title.empty())) {
93 return false;
94 }
95
kristiparkbe00c6562018-08-01 21:30:2796 // 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
kristipark940dc202018-07-30 18:29:55103 auto it = FindLinkWithUrl(url);
104 if (it == current_links_.end())
105 return false;
106
kristiparkbe00c6562018-08-01 21:30:27107 // At this point, we will be modifying at least one of the values.
Kristi Park6d2d9982018-10-25 00:16:58108 previous_links_ = current_links_;
kristipark940dc202018-07-30 18:29:55109
kristiparkbe00c6562018-08-01 21:30:27110 if (!new_url.is_empty())
111 it->url = new_url;
kristipark940dc202018-07-30 18:29:55112 if (!new_title.empty())
113 it->title = new_title;
kristiparke79bcc32018-08-29 19:02:31114 it->is_most_visited = false;
kristipark940dc202018-07-30 18:29:55115
Kristi Parkd7db601c2018-10-08 19:23:10116 StoreLinks();
kristipark940dc202018-07-30 18:29:55117 return true;
118}
119
Kristi Park8c8047ec2018-10-31 22:17:29120bool 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
kristiparkc0199772018-07-18 05:25:42149bool 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
kristiparkbe00c6562018-08-01 21:30:27157 previous_links_ = current_links_;
kristiparkc0199772018-07-18 05:25:42158 current_links_.erase(it);
Kristi Parkd7db601c2018-10-08 19:23:10159 StoreLinks();
kristiparkc0199772018-07-18 05:25:42160 return true;
161}
162
kristiparkbe00c6562018-08-01 21:30:27163bool CustomLinksManagerImpl::UndoAction() {
164 if (!IsInitialized() || !previous_links_.has_value())
kristiparkc0199772018-07-18 05:25:42165 return false;
kristiparkc0199772018-07-18 05:25:42166
kristiparkbe00c6562018-08-01 21:30:27167 // Replace the current links with the previous state.
168 current_links_ = *previous_links_;
169 previous_links_ = base::nullopt;
Kristi Parkd7db601c2018-10-08 19:23:10170 StoreLinks();
kristiparkc0199772018-07-18 05:25:42171 return true;
172}
173
174void CustomLinksManagerImpl::ClearLinks() {
Kristi Parkd7db601c2018-10-08 19:23:10175 {
176 base::AutoReset<bool> auto_reset(&updating_preferences_, true);
177 store_.ClearLinks();
178 }
kristiparkc0199772018-07-18 05:25:42179 current_links_.clear();
kristiparkbe00c6562018-08-01 21:30:27180 previous_links_ = base::nullopt;
kristiparkc0199772018-07-18 05:25:42181}
182
Kristi Parkd7db601c2018-10-08 19:23:10183void CustomLinksManagerImpl::StoreLinks() {
184 base::AutoReset<bool> auto_reset(&updating_preferences_, true);
185 store_.StoreLinks(current_links_);
186}
187
kristiparkc0199772018-07-18 05:25:42188std::vector<CustomLinksManager::Link>::iterator
189CustomLinksManagerImpl::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
kristiparke79bcc32018-08-29 19:02:31194std::unique_ptr<base::CallbackList<void()>::Subscription>
195CustomLinksManagerImpl::RegisterCallbackForOnChanged(
196 base::RepeatingClosure callback) {
197 return callback_list_.Add(callback);
198}
199
200// history::HistoryServiceObserver implementation.
201void 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 Parkd7db601c2018-10-08 19:23:10219 StoreLinks();
kristiparke79bcc32018-08-29 19:02:31220 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
227void CustomLinksManagerImpl::HistoryServiceBeingDeleted(
228 history::HistoryService* history_service) {
229 history_service_observer_.RemoveAll();
230}
231
Kristi Parkd7db601c2018-10-08 19:23:10232void 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
kristiparkc0199772018-07-18 05:25:42244// static
245void CustomLinksManagerImpl::RegisterProfilePrefs(
246 user_prefs::PrefRegistrySyncable* user_prefs) {
Kristi Parkd7db601c2018-10-08 19:23:10247 user_prefs->RegisterBooleanPref(
248 prefs::kCustomLinksInitialized, false,
249 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
kristiparkc0199772018-07-18 05:25:42250 CustomLinksStore::RegisterProfilePrefs(user_prefs);
251}
252
253} // namespace ntp_tiles