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 17 using content::BrowserThread; 18 19 namespace { 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. 27 struct PartnerModelKeeper { 28 scoped_ptr<BookmarkNode> partner_bookmarks_root; 29 bool loaded; 30 31 PartnerModelKeeper() 32 : loaded(false) {} 33 }; 34 35 base::LazyInstance<PartnerModelKeeper> g_partner_model_keeper = 36 LAZY_INSTANCE_INITIALIZER; 37 38 const void* kPartnerBookmarksShimUserDataKey = 39 &kPartnerBookmarksShimUserDataKey; 40 41 // Dictionary keys for entries in the kPartnerBookmarksMapping pref. 42 static const char kMappingUrl[] = "url"; 43 static const char kMappingProviderTitle[] = "provider_title"; 44 static const char kMappingTitle[] = "mapped_title"; 45 46 static bool g_disable_partner_bookmarks_editing = false; 47 48 } // namespace 49 50 // static 51 PartnerBookmarksShim* 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 69 void PartnerBookmarksShim::RegisterProfilePrefs( 70 user_prefs::PrefRegistrySyncable* registry) { 71 registry->RegisterListPref( 72 prefs::kPartnerBookmarkMappings, 73 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 74 } 75 76 // static 77 void PartnerBookmarksShim::DisablePartnerBookmarksEditing() { 78 g_disable_partner_bookmarks_editing = true; 79 } 80 81 bool PartnerBookmarksShim::IsLoaded() const { 82 return g_partner_model_keeper.Get().loaded; 83 } 84 85 bool PartnerBookmarksShim::HasPartnerBookmarks() const { 86 DCHECK(IsLoaded()); 87 return g_partner_model_keeper.Get().partner_bookmarks_root.get() != NULL; 88 } 89 90 bool 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 105 bool 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 114 void PartnerBookmarksShim::RemoveBookmark(const BookmarkNode* node) { 115 DCHECK(IsEditable(node)); 116 RenameBookmark(node, base::string16()); 117 } 118 119 void 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 129 void PartnerBookmarksShim::AddObserver( 130 PartnerBookmarksShim::Observer* observer) { 131 observers_.AddObserver(observer); 132 } 133 134 void PartnerBookmarksShim::RemoveObserver( 135 PartnerBookmarksShim::Observer* observer) { 136 observers_.RemoveObserver(observer); 137 } 138 139 const BookmarkNode* PartnerBookmarksShim::GetNodeByID(int64 id) const { 140 DCHECK(IsLoaded()); 141 if (!HasPartnerBookmarks()) 142 return NULL; 143 return GetNodeByID(GetPartnerBookmarksRoot(), id); 144 } 145 146 base::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 160 bool 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 173 const BookmarkNode* PartnerBookmarksShim::GetPartnerBookmarksRoot() const { 174 return g_partner_model_keeper.Get().partner_bookmarks_root.get(); 175 } 176 177 void 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 185 PartnerBookmarksShim::NodeRenamingMapKey::NodeRenamingMapKey( 186 const GURL& url, const base::string16& provider_title) 187 : url_(url), provider_title_(provider_title) {} 188 189 PartnerBookmarksShim::NodeRenamingMapKey::~NodeRenamingMapKey() {} 190 191 bool 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 198 void PartnerBookmarksShim::ClearInBrowserContextForTesting( 199 content::BrowserContext* browser_context) { 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 201 browser_context->SetUserData(kPartnerBookmarksShimUserDataKey, 0); 202 } 203 204 // static 205 void 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 211 void PartnerBookmarksShim::EnablePartnerBookmarksEditing() { 212 g_disable_partner_bookmarks_editing = false; 213 } 214 215 PartnerBookmarksShim::PartnerBookmarksShim(PrefService* prefs) 216 : prefs_(prefs), 217 observers_( 218 ObserverList<PartnerBookmarksShim::Observer>::NOTIFY_EXISTING_ONLY) { 219 } 220 221 PartnerBookmarksShim::~PartnerBookmarksShim() { 222 FOR_EACH_OBSERVER(PartnerBookmarksShim::Observer, observers_, 223 ShimBeingDeleted(this)); 224 } 225 226 const 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 238 void 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 273 void 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