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 16 using content::BrowserThread; 17 18 namespace { 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. 26 struct PartnerModelKeeper { 27 scoped_ptr<BookmarkNode> partner_bookmarks_root; 28 bool loaded; 29 30 PartnerModelKeeper() 31 : loaded(false) {} 32 }; 33 34 base::LazyInstance<PartnerModelKeeper> g_partner_model_keeper = 35 LAZY_INSTANCE_INITIALIZER; 36 37 const void* kPartnerBookmarksShimUserDataKey = 38 &kPartnerBookmarksShimUserDataKey; 39 40 // Dictionary keys for entries in the kPartnerBookmarksMapping pref. 41 static const char kMappingUrl[] = "url"; 42 static const char kMappingProviderTitle[] = "provider_title"; 43 static const char kMappingTitle[] = "mapped_title"; 44 45 } // namespace 46 47 // static 48 PartnerBookmarksShim* 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 66 void PartnerBookmarksShim::RegisterProfilePrefs( 67 user_prefs::PrefRegistrySyncable* registry) { 68 registry->RegisterListPref( 69 prefs::kPartnerBookmarkMappings, 70 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 71 } 72 73 bool PartnerBookmarksShim::IsLoaded() const { 74 return g_partner_model_keeper.Get().loaded; 75 } 76 77 bool PartnerBookmarksShim::HasPartnerBookmarks() const { 78 DCHECK(IsLoaded()); 79 return g_partner_model_keeper.Get().partner_bookmarks_root.get() != NULL; 80 } 81 82 bool 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 95 void PartnerBookmarksShim::RemoveBookmark(const BookmarkNode* node) { 96 RenameBookmark(node, base::string16()); 97 } 98 99 void 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 108 void PartnerBookmarksShim::AddObserver( 109 PartnerBookmarksShim::Observer* observer) { 110 observers_.AddObserver(observer); 111 } 112 113 void PartnerBookmarksShim::RemoveObserver( 114 PartnerBookmarksShim::Observer* observer) { 115 observers_.RemoveObserver(observer); 116 } 117 118 const BookmarkNode* PartnerBookmarksShim::GetNodeByID(int64 id) const { 119 DCHECK(IsLoaded()); 120 if (!HasPartnerBookmarks()) 121 return NULL; 122 return GetNodeByID(GetPartnerBookmarksRoot(), id); 123 } 124 125 base::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 137 bool 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 150 const BookmarkNode* PartnerBookmarksShim::GetPartnerBookmarksRoot() const { 151 return g_partner_model_keeper.Get().partner_bookmarks_root.get(); 152 } 153 154 void 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 162 PartnerBookmarksShim::NodeRenamingMapKey::NodeRenamingMapKey( 163 const GURL& url, const base::string16& provider_title) 164 : url_(url), provider_title_(provider_title) {} 165 166 PartnerBookmarksShim::NodeRenamingMapKey::~NodeRenamingMapKey() {} 167 168 bool 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 175 void PartnerBookmarksShim::ClearInBrowserContextForTesting( 176 content::BrowserContext* browser_context) { 177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 178 browser_context->SetUserData(kPartnerBookmarksShimUserDataKey, 0); 179 } 180 181 // static 182 void PartnerBookmarksShim::ClearPartnerModelForTesting() { 183 g_partner_model_keeper.Get().loaded = false; 184 g_partner_model_keeper.Get().partner_bookmarks_root.reset(0); 185 } 186 187 PartnerBookmarksShim::PartnerBookmarksShim(PrefService* prefs) 188 : prefs_(prefs), 189 observers_( 190 ObserverList<PartnerBookmarksShim::Observer>::NOTIFY_EXISTING_ONLY) { 191 } 192 193 PartnerBookmarksShim::~PartnerBookmarksShim() { 194 FOR_EACH_OBSERVER(PartnerBookmarksShim::Observer, observers_, 195 ShimBeingDeleted(this)); 196 } 197 198 const 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 210 void 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 245 void 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