1// Copyright 2014 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#include "components/enhanced_bookmarks/bookmark_image_service.h" 5 6#include "base/single_thread_task_runner.h" 7#include "base/thread_task_runner_handle.h" 8#include "base/threading/sequenced_worker_pool.h" 9#include "components/bookmarks/browser/bookmark_model.h" 10#include "components/bookmarks/browser/bookmark_model_observer.h" 11#include "components/enhanced_bookmarks/enhanced_bookmark_model.h" 12#include "components/enhanced_bookmarks/enhanced_bookmark_utils.h" 13#include "components/enhanced_bookmarks/persistent_image_store.h" 14 15namespace { 16 17const char kSequenceToken[] = "BookmarkImagesSequenceToken"; 18 19void ConstructPersistentImageStore(PersistentImageStore* store, 20 const base::FilePath& path) { 21 DCHECK(store); 22 new (store) PersistentImageStore(path); 23} 24 25void DeleteImageStore(ImageStore* store) { 26 DCHECK(store); 27 delete store; 28} 29 30void RetrieveImageFromStoreRelay( 31 ImageStore* store, 32 const GURL& page_url, 33 enhanced_bookmarks::BookmarkImageService::Callback callback, 34 scoped_refptr<base::SingleThreadTaskRunner> origin_loop) { 35 std::pair<gfx::Image, GURL> image_data = store->Get(page_url); 36 origin_loop->PostTask( 37 FROM_HERE, base::Bind(callback, image_data.first, image_data.second)); 38} 39 40} // namespace 41 42namespace enhanced_bookmarks { 43BookmarkImageService::BookmarkImageService( 44 scoped_ptr<ImageStore> store, 45 EnhancedBookmarkModel* enhanced_bookmark_model, 46 scoped_refptr<base::SequencedWorkerPool> pool) 47 : enhanced_bookmark_model_(enhanced_bookmark_model), 48 store_(store.Pass()), 49 pool_(pool) { 50 DCHECK(CalledOnValidThread()); 51 enhanced_bookmark_model_->bookmark_model()->AddObserver(this); 52} 53 54BookmarkImageService::BookmarkImageService( 55 const base::FilePath& path, 56 EnhancedBookmarkModel* enhanced_bookmark_model, 57 scoped_refptr<base::SequencedWorkerPool> pool) 58 : enhanced_bookmark_model_(enhanced_bookmark_model), pool_(pool) { 59 DCHECK(CalledOnValidThread()); 60 // PersistentImageStore has to be constructed in the thread it will be used, 61 // so we are posting the construction to the thread. However, we first 62 // allocate memory and keep here. The reason is that, before 63 // PersistentImageStore construction is done, it's possible that 64 // another member function, that posts store_ to the thread, is called. 65 // Although the construction might not be finished yet, we still want to post 66 // the task since it's guaranteed to be constructed by the time it is used, by 67 // the sequential thread task pool. 68 // 69 // Other alternatives: 70 // - Using a lock or WaitableEvent for PersistentImageStore construction. 71 // But waiting on UI thread is discouraged. 72 // - Posting the current BookmarkImageService instance instead of store_. 73 // But this will require using a weak pointer and can potentially block 74 // destroying BookmarkImageService. 75 PersistentImageStore* store = 76 (PersistentImageStore*)::operator new(sizeof(PersistentImageStore)); 77 store_.reset(store); 78 pool_->PostNamedSequencedWorkerTask( 79 kSequenceToken, 80 FROM_HERE, 81 base::Bind(&ConstructPersistentImageStore, store, path)); 82} 83 84BookmarkImageService::~BookmarkImageService() { 85 DCHECK(CalledOnValidThread()); 86 pool_->PostNamedSequencedWorkerTask( 87 kSequenceToken, 88 FROM_HERE, 89 base::Bind(&DeleteImageStore, store_.release())); 90} 91 92void BookmarkImageService::Shutdown() { 93 DCHECK(CalledOnValidThread()); 94 enhanced_bookmark_model_->bookmark_model()->RemoveObserver(this); 95 enhanced_bookmark_model_ = NULL; 96} 97 98void BookmarkImageService::SalientImageForUrl(const GURL& page_url, 99 Callback callback) { 100 DCHECK(CalledOnValidThread()); 101 SalientImageForUrl(page_url, true, callback); 102} 103 104void BookmarkImageService::RetrieveImageFromStore( 105 const GURL& page_url, 106 BookmarkImageService::Callback callback) { 107 DCHECK(CalledOnValidThread()); 108 pool_->PostSequencedWorkerTaskWithShutdownBehavior( 109 pool_->GetNamedSequenceToken(kSequenceToken), 110 FROM_HERE, 111 base::Bind(&RetrieveImageFromStoreRelay, 112 base::Unretained(store_.get()), 113 page_url, 114 callback, 115 base::ThreadTaskRunnerHandle::Get()), 116 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 117} 118 119void BookmarkImageService::RetrieveSalientImageForPageUrl( 120 const GURL& page_url) { 121 DCHECK(CalledOnValidThread()); 122 if (IsPageUrlInProgress(page_url)) 123 return; // A request for this URL is already in progress. 124 125 in_progress_page_urls_.insert(page_url); 126 127 const BookmarkNode* bookmark = 128 enhanced_bookmark_model_->bookmark_model() 129 ->GetMostRecentlyAddedUserNodeForURL(page_url); 130 GURL image_url; 131 if (bookmark) { 132 int width; 133 int height; 134 enhanced_bookmark_model_->GetThumbnailImage( 135 bookmark, &image_url, &width, &height); 136 } 137 138 RetrieveSalientImage( 139 page_url, 140 image_url, 141 "", 142 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE, 143 false); 144} 145 146void BookmarkImageService::FetchCallback(const GURL& page_url, 147 Callback original_callback, 148 const gfx::Image& image, 149 const GURL& image_url) { 150 DCHECK(CalledOnValidThread()); 151 if (!image.IsEmpty() || !image_url.is_empty()) { 152 // Either the image was in the store or there is no image in the store, but 153 // an URL for an image is present, indicating that a previous attempt to 154 // download the image failed. Just return the image. 155 original_callback.Run(image, image_url); 156 } else { 157 // There is no image in the store, and no previous attempts to retrieve 158 // one. Start a request to retrieve a salient image if there is an image 159 // url set on a bookmark, and then enqueue the request for the image to 160 // be triggered when the retrieval is finished. 161 RetrieveSalientImageForPageUrl(page_url); 162 SalientImageForUrl(page_url, false, original_callback); 163 } 164} 165 166void BookmarkImageService::SalientImageForUrl(const GURL& page_url, 167 bool fetch_from_bookmark, 168 Callback callback) { 169 DCHECK(CalledOnValidThread()); 170 171 // If the request is done while the image is currently being retrieved, just 172 // store the appropriate callbacks to call once the image is retrieved. 173 if (IsPageUrlInProgress(page_url)) { 174 callbacks_[page_url].push_back(callback); 175 return; 176 } 177 178 if (!fetch_from_bookmark) { 179 RetrieveImageFromStore(page_url, callback); 180 } else { 181 RetrieveImageFromStore(page_url, 182 base::Bind(&BookmarkImageService::FetchCallback, 183 base::Unretained(this), 184 page_url, 185 callback)); 186 } 187} 188 189void BookmarkImageService::ProcessNewImage(const GURL& page_url, 190 bool update_bookmarks, 191 const gfx::Image& image, 192 const GURL& image_url) { 193 DCHECK(CalledOnValidThread()); 194 StoreImage(image, image_url, page_url); 195 in_progress_page_urls_.erase(page_url); 196 ProcessRequests(page_url, image, image_url); 197 if (update_bookmarks && image_url.is_valid()) { 198 const BookmarkNode* bookmark = 199 enhanced_bookmark_model_->bookmark_model() 200 ->GetMostRecentlyAddedUserNodeForURL(page_url); 201 if (bookmark) { 202 const gfx::Size& size = image.Size(); 203 bool result = enhanced_bookmark_model_->SetOriginalImage( 204 bookmark, image_url, size.width(), size.height()); 205 DCHECK(result); 206 } 207 } 208} 209 210bool BookmarkImageService::IsPageUrlInProgress(const GURL& page_url) { 211 DCHECK(CalledOnValidThread()); 212 return in_progress_page_urls_.find(page_url) != in_progress_page_urls_.end(); 213} 214 215void BookmarkImageService::StoreImage(const gfx::Image& image, 216 const GURL& image_url, 217 const GURL& page_url) { 218 DCHECK(CalledOnValidThread()); 219 if (!image.IsEmpty()) { 220 pool_->PostNamedSequencedWorkerTask( 221 kSequenceToken, 222 FROM_HERE, 223 base::Bind(&ImageStore::Insert, 224 base::Unretained(store_.get()), 225 page_url, 226 image_url, 227 image)); 228 } 229} 230 231void BookmarkImageService::RemoveImageForUrl(const GURL& page_url) { 232 DCHECK(CalledOnValidThread()); 233 pool_->PostNamedSequencedWorkerTask( 234 kSequenceToken, 235 FROM_HERE, 236 base::Bind(&ImageStore::Erase, base::Unretained(store_.get()), page_url)); 237 in_progress_page_urls_.erase(page_url); 238 ProcessRequests(page_url, gfx::Image(), GURL()); 239} 240 241void BookmarkImageService::ChangeImageURL(const GURL& from, const GURL& to) { 242 DCHECK(CalledOnValidThread()); 243 pool_->PostNamedSequencedWorkerTask(kSequenceToken, 244 FROM_HERE, 245 base::Bind(&ImageStore::ChangeImageURL, 246 base::Unretained(store_.get()), 247 from, 248 to)); 249 in_progress_page_urls_.erase(from); 250 ProcessRequests(from, gfx::Image(), GURL()); 251} 252 253void BookmarkImageService::ClearAll() { 254 DCHECK(CalledOnValidThread()); 255 // Clears and executes callbacks. 256 pool_->PostNamedSequencedWorkerTask( 257 kSequenceToken, 258 FROM_HERE, 259 base::Bind(&ImageStore::ClearAll, base::Unretained(store_.get()))); 260 261 for (std::map<const GURL, std::vector<Callback> >::const_iterator it = 262 callbacks_.begin(); 263 it != callbacks_.end(); 264 ++it) { 265 ProcessRequests(it->first, gfx::Image(), GURL()); 266 } 267 268 in_progress_page_urls_.erase(in_progress_page_urls_.begin(), 269 in_progress_page_urls_.end()); 270} 271 272void BookmarkImageService::ProcessRequests(const GURL& page_url, 273 const gfx::Image& image, 274 const GURL& image_url) { 275 DCHECK(CalledOnValidThread()); 276 277 std::vector<Callback> callbacks = callbacks_[page_url]; 278 for (std::vector<Callback>::const_iterator it = callbacks.begin(); 279 it != callbacks.end(); 280 ++it) { 281 it->Run(image, image_url); 282 } 283 284 callbacks_.erase(page_url); 285} 286 287// BookmarkModelObserver methods. 288 289void BookmarkImageService::BookmarkNodeRemoved( 290 BookmarkModel* model, 291 const BookmarkNode* parent, 292 int old_index, 293 const BookmarkNode* node, 294 const std::set<GURL>& removed_urls) { 295 DCHECK(CalledOnValidThread()); 296 for (std::set<GURL>::const_iterator iter = removed_urls.begin(); 297 iter != removed_urls.end(); 298 ++iter) { 299 RemoveImageForUrl(*iter); 300 } 301} 302 303void BookmarkImageService::BookmarkModelLoaded(BookmarkModel* model, 304 bool ids_reassigned) { 305} 306 307void BookmarkImageService::BookmarkNodeMoved(BookmarkModel* model, 308 const BookmarkNode* old_parent, 309 int old_index, 310 const BookmarkNode* new_parent, 311 int new_index) { 312} 313 314void BookmarkImageService::BookmarkNodeAdded(BookmarkModel* model, 315 const BookmarkNode* parent, 316 int index) { 317} 318 319void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel* model, 320 const BookmarkNode* node) { 321 DCHECK(CalledOnValidThread()); 322 if (node->is_url()) 323 previous_url_ = node->url(); 324} 325 326void BookmarkImageService::BookmarkNodeChanged(BookmarkModel* model, 327 const BookmarkNode* node) { 328 DCHECK(CalledOnValidThread()); 329 if (node->is_url() && previous_url_ != node->url()) 330 ChangeImageURL(previous_url_, node->url()); 331} 332 333void BookmarkImageService::BookmarkNodeFaviconChanged( 334 BookmarkModel* model, 335 const BookmarkNode* node) { 336} 337 338void BookmarkImageService::BookmarkNodeChildrenReordered( 339 BookmarkModel* model, 340 const BookmarkNode* node) { 341} 342 343void BookmarkImageService::BookmarkAllUserNodesRemoved( 344 BookmarkModel* model, 345 const std::set<GURL>& removed_urls) { 346 ClearAll(); 347} 348 349} // namespace enhanced_bookmarks 350