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 15 namespace { 16 17 const char kSequenceToken[] = "BookmarkImagesSequenceToken"; 18 19 void ConstructPersistentImageStore(PersistentImageStore* store, 20 const base::FilePath& path) { 21 DCHECK(store); 22 new (store) PersistentImageStore(path); 23 } 24 25 void DeleteImageStore(ImageStore* store) { 26 DCHECK(store); 27 delete store; 28 } 29 30 void 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 42 namespace enhanced_bookmarks { 43 BookmarkImageService::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 54 BookmarkImageService::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 84 BookmarkImageService::~BookmarkImageService() { 85 DCHECK(CalledOnValidThread()); 86 pool_->PostNamedSequencedWorkerTask( 87 kSequenceToken, 88 FROM_HERE, 89 base::Bind(&DeleteImageStore, store_.release())); 90 } 91 92 void BookmarkImageService::Shutdown() { 93 DCHECK(CalledOnValidThread()); 94 enhanced_bookmark_model_->bookmark_model()->RemoveObserver(this); 95 enhanced_bookmark_model_ = NULL; 96 } 97 98 void BookmarkImageService::SalientImageForUrl(const GURL& page_url, 99 Callback callback) { 100 DCHECK(CalledOnValidThread()); 101 SalientImageForUrl(page_url, true, callback); 102 } 103 104 void 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 119 void 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 146 void 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 166 void 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 189 void 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 210 bool 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 215 void 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 231 void 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 241 void 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 253 void 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 272 void 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 289 void 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 303 void BookmarkImageService::BookmarkModelLoaded(BookmarkModel* model, 304 bool ids_reassigned) { 305 } 306 307 void BookmarkImageService::BookmarkNodeMoved(BookmarkModel* model, 308 const BookmarkNode* old_parent, 309 int old_index, 310 const BookmarkNode* new_parent, 311 int new_index) { 312 } 313 314 void BookmarkImageService::BookmarkNodeAdded(BookmarkModel* model, 315 const BookmarkNode* parent, 316 int index) { 317 } 318 319 void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel* model, 320 const BookmarkNode* node) { 321 DCHECK(CalledOnValidThread()); 322 if (node->is_url()) 323 previous_url_ = node->url(); 324 } 325 326 void 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 333 void BookmarkImageService::BookmarkNodeFaviconChanged( 334 BookmarkModel* model, 335 const BookmarkNode* node) { 336 } 337 338 void BookmarkImageService::BookmarkNodeChildrenReordered( 339 BookmarkModel* model, 340 const BookmarkNode* node) { 341 } 342 343 void BookmarkImageService::BookmarkAllUserNodesRemoved( 344 BookmarkModel* model, 345 const std::set<GURL>& removed_urls) { 346 ClearAll(); 347 } 348 349 } // namespace enhanced_bookmarks 350