1// Copyright 2013 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 "chrome/browser/android/bookmarks/partner_bookmarks_shim.h"
6
7#include "base/lazy_instance.h"
8#include "base/prefs/pref_service.h"
9#include "base/values.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/common/pref_names.h"
12#include "components/bookmarks/browser/bookmark_model.h"
13#include "components/pref_registry/pref_registry_syncable.h"
14#include "content/public/browser/browser_context.h"
15#include "content/public/browser/browser_thread.h"
16
17using content::BrowserThread;
18
19namespace {
20
21// PartnerModelKeeper is used as a singleton to store an immutable hierarchy
22// of partner bookmarks.  The hierarchy is retrieved from the partner bookmarks
23// provider and doesn't depend on the user profile.
24// The retrieved hierarchy persists
25// PartnerBookmarksShim is responsible to applying and storing the user changes
26// (deletions/renames) in the user profile, thus keeping the hierarchy intact.
27struct PartnerModelKeeper {
28  scoped_ptr<BookmarkNode> partner_bookmarks_root;
29  bool loaded;
30
31  PartnerModelKeeper()
32    : loaded(false) {}
33};
34
35base::LazyInstance<PartnerModelKeeper> g_partner_model_keeper =
36    LAZY_INSTANCE_INITIALIZER;
37
38const void* kPartnerBookmarksShimUserDataKey =
39    &kPartnerBookmarksShimUserDataKey;
40
41// Dictionary keys for entries in the kPartnerBookmarksMapping pref.
42static const char kMappingUrl[] = "url";
43static const char kMappingProviderTitle[] = "provider_title";
44static const char kMappingTitle[] = "mapped_title";
45
46static bool g_disable_partner_bookmarks_editing = false;
47
48}  // namespace
49
50// static
51PartnerBookmarksShim* PartnerBookmarksShim::BuildForBrowserContext(
52    content::BrowserContext* browser_context) {
53  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
54
55  PartnerBookmarksShim* data =
56      reinterpret_cast<PartnerBookmarksShim*>(
57          browser_context->GetUserData(kPartnerBookmarksShimUserDataKey));
58  if (data)
59    return data;
60
61  data = new PartnerBookmarksShim(
62      Profile::FromBrowserContext(browser_context)->GetPrefs());
63  browser_context->SetUserData(kPartnerBookmarksShimUserDataKey, data);
64  data->ReloadNodeMapping();
65  return data;
66}
67
68// static
69void PartnerBookmarksShim::RegisterProfilePrefs(
70    user_prefs::PrefRegistrySyncable* registry) {
71  registry->RegisterListPref(
72      prefs::kPartnerBookmarkMappings,
73      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
74}
75
76// static
77void PartnerBookmarksShim::DisablePartnerBookmarksEditing() {
78  g_disable_partner_bookmarks_editing = true;
79}
80
81bool PartnerBookmarksShim::IsLoaded() const {
82  return g_partner_model_keeper.Get().loaded;
83}
84
85bool PartnerBookmarksShim::HasPartnerBookmarks() const {
86  DCHECK(IsLoaded());
87  return g_partner_model_keeper.Get().partner_bookmarks_root.get() != NULL;
88}
89
90bool PartnerBookmarksShim::IsReachable(const BookmarkNode* node) const {
91  DCHECK(IsPartnerBookmark(node));
92  if (!HasPartnerBookmarks())
93    return false;
94  if (!g_disable_partner_bookmarks_editing) {
95    for (const BookmarkNode* i = node; i != NULL; i = i->parent()) {
96      const NodeRenamingMapKey key(i->url(), i->GetTitle());
97      NodeRenamingMap::const_iterator remap = node_rename_remove_map_.find(key);
98      if (remap != node_rename_remove_map_.end() && remap->second.empty())
99        return false;
100    }
101  }
102  return true;
103}
104
105bool PartnerBookmarksShim::IsEditable(const BookmarkNode* node) const {
106  DCHECK(IsPartnerBookmark(node));
107  if (!HasPartnerBookmarks())
108    return false;
109  if (g_disable_partner_bookmarks_editing)
110    return false;
111  return true;
112}
113
114void PartnerBookmarksShim::RemoveBookmark(const BookmarkNode* node) {
115  DCHECK(IsEditable(node));
116  RenameBookmark(node, base::string16());
117}
118
119void PartnerBookmarksShim::RenameBookmark(const BookmarkNode* node,
120                                          const base::string16& title) {
121  DCHECK(IsEditable(node));
122  const NodeRenamingMapKey key(node->url(), node->GetTitle());
123  node_rename_remove_map_[key] = title;
124  SaveNodeMapping();
125  FOR_EACH_OBSERVER(PartnerBookmarksShim::Observer, observers_,
126                    PartnerShimChanged(this));
127}
128
129void PartnerBookmarksShim::AddObserver(
130    PartnerBookmarksShim::Observer* observer) {
131  observers_.AddObserver(observer);
132}
133
134void PartnerBookmarksShim::RemoveObserver(
135    PartnerBookmarksShim::Observer* observer) {
136  observers_.RemoveObserver(observer);
137}
138
139const BookmarkNode* PartnerBookmarksShim::GetNodeByID(int64 id) const {
140  DCHECK(IsLoaded());
141  if (!HasPartnerBookmarks())
142    return NULL;
143  return GetNodeByID(GetPartnerBookmarksRoot(), id);
144}
145
146base::string16 PartnerBookmarksShim::GetTitle(const BookmarkNode* node) const {
147  DCHECK(node);
148  DCHECK(IsPartnerBookmark(node));
149
150  if (!g_disable_partner_bookmarks_editing) {
151    const NodeRenamingMapKey key(node->url(), node->GetTitle());
152    NodeRenamingMap::const_iterator i = node_rename_remove_map_.find(key);
153    if (i != node_rename_remove_map_.end())
154      return i->second;
155  }
156
157  return node->GetTitle();
158}
159
160bool PartnerBookmarksShim::IsPartnerBookmark(const BookmarkNode* node) const {
161  DCHECK(IsLoaded());
162  if (!HasPartnerBookmarks())
163    return false;
164  const BookmarkNode* parent = node;
165  while (parent) {
166    if (parent == GetPartnerBookmarksRoot())
167      return true;
168    parent = parent->parent();
169  }
170  return false;
171}
172
173const BookmarkNode* PartnerBookmarksShim::GetPartnerBookmarksRoot() const {
174  return g_partner_model_keeper.Get().partner_bookmarks_root.get();
175}
176
177void PartnerBookmarksShim::SetPartnerBookmarksRoot(BookmarkNode* root_node) {
178  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179  g_partner_model_keeper.Get().partner_bookmarks_root.reset(root_node);
180  g_partner_model_keeper.Get().loaded = true;
181  FOR_EACH_OBSERVER(PartnerBookmarksShim::Observer, observers_,
182                    PartnerShimLoaded(this));
183}
184
185PartnerBookmarksShim::NodeRenamingMapKey::NodeRenamingMapKey(
186    const GURL& url, const base::string16& provider_title)
187    : url_(url), provider_title_(provider_title) {}
188
189PartnerBookmarksShim::NodeRenamingMapKey::~NodeRenamingMapKey() {}
190
191bool operator<(const PartnerBookmarksShim::NodeRenamingMapKey& a,
192               const PartnerBookmarksShim::NodeRenamingMapKey& b) {
193  return (a.url_ < b.url_) ||
194      (a.url_ == b.url_ && a.provider_title_ < b.provider_title_);
195}
196
197// static
198void PartnerBookmarksShim::ClearInBrowserContextForTesting(
199    content::BrowserContext* browser_context) {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201  browser_context->SetUserData(kPartnerBookmarksShimUserDataKey, 0);
202}
203
204// static
205void PartnerBookmarksShim::ClearPartnerModelForTesting() {
206  g_partner_model_keeper.Get().loaded = false;
207  g_partner_model_keeper.Get().partner_bookmarks_root.reset(0);
208}
209
210// static
211void PartnerBookmarksShim::EnablePartnerBookmarksEditing() {
212  g_disable_partner_bookmarks_editing = false;
213}
214
215PartnerBookmarksShim::PartnerBookmarksShim(PrefService* prefs)
216    : prefs_(prefs),
217      observers_(
218          ObserverList<PartnerBookmarksShim::Observer>::NOTIFY_EXISTING_ONLY) {
219}
220
221PartnerBookmarksShim::~PartnerBookmarksShim() {
222  FOR_EACH_OBSERVER(PartnerBookmarksShim::Observer, observers_,
223                    ShimBeingDeleted(this));
224}
225
226const BookmarkNode* PartnerBookmarksShim::GetNodeByID(
227    const BookmarkNode* parent, int64 id) const {
228  if (parent->id() == id)
229    return parent;
230  for (int i = 0, child_count = parent->child_count(); i < child_count; ++i) {
231    const BookmarkNode* result = GetNodeByID(parent->GetChild(i), id);
232    if (result)
233      return result;
234  }
235  return NULL;
236}
237
238void PartnerBookmarksShim::ReloadNodeMapping() {
239  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240
241  node_rename_remove_map_.clear();
242  if (!prefs_)
243    return;
244
245  const base::ListValue* list =
246      prefs_->GetList(prefs::kPartnerBookmarkMappings);
247  if (!list)
248    return;
249
250  for (base::ListValue::const_iterator it = list->begin();
251       it != list->end(); ++it) {
252    const base::DictionaryValue* dict = NULL;
253    if (!*it || !(*it)->GetAsDictionary(&dict)) {
254      NOTREACHED();
255      continue;
256    }
257
258    std::string url;
259    base::string16 provider_title;
260    base::string16 mapped_title;
261    if (!dict->GetString(kMappingUrl, &url) ||
262        !dict->GetString(kMappingProviderTitle, &provider_title) ||
263        !dict->GetString(kMappingTitle, &mapped_title)) {
264      NOTREACHED();
265      continue;
266    }
267
268    const NodeRenamingMapKey key(GURL(url), provider_title);
269    node_rename_remove_map_[key] = mapped_title;
270  }
271}
272
273void PartnerBookmarksShim::SaveNodeMapping() {
274  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
275  if (!prefs_)
276    return;
277
278  base::ListValue list;
279  for (NodeRenamingMap::const_iterator i = node_rename_remove_map_.begin();
280       i != node_rename_remove_map_.end();
281       ++i) {
282    base::DictionaryValue* dict = new base::DictionaryValue();
283    dict->SetString(kMappingUrl, i->first.url().spec());
284    dict->SetString(kMappingProviderTitle, i->first.provider_title());
285    dict->SetString(kMappingTitle, i->second);
286    list.Append(dict);
287  }
288  prefs_->Set(prefs::kPartnerBookmarkMappings, list);
289}
290