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