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