// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/enhanced_bookmarks/bookmark_image_service.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "base/threading/sequenced_worker_pool.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_model_observer.h" #include "components/enhanced_bookmarks/enhanced_bookmark_model.h" #include "components/enhanced_bookmarks/enhanced_bookmark_utils.h" #include "components/enhanced_bookmarks/persistent_image_store.h" namespace { const char kSequenceToken[] = "BookmarkImagesSequenceToken"; void ConstructPersistentImageStore(PersistentImageStore* store, const base::FilePath& path) { DCHECK(store); new (store) PersistentImageStore(path); } void DeleteImageStore(ImageStore* store) { DCHECK(store); delete store; } void RetrieveImageFromStoreRelay( ImageStore* store, const GURL& page_url, enhanced_bookmarks::BookmarkImageService::Callback callback, scoped_refptr<base::SingleThreadTaskRunner> origin_loop) { std::pair<gfx::Image, GURL> image_data = store->Get(page_url); origin_loop->PostTask( FROM_HERE, base::Bind(callback, image_data.first, image_data.second)); } } // namespace namespace enhanced_bookmarks { BookmarkImageService::BookmarkImageService( scoped_ptr<ImageStore> store, EnhancedBookmarkModel* enhanced_bookmark_model, scoped_refptr<base::SequencedWorkerPool> pool) : enhanced_bookmark_model_(enhanced_bookmark_model), store_(store.Pass()), pool_(pool) { DCHECK(CalledOnValidThread()); enhanced_bookmark_model_->bookmark_model()->AddObserver(this); } BookmarkImageService::BookmarkImageService( const base::FilePath& path, EnhancedBookmarkModel* enhanced_bookmark_model, scoped_refptr<base::SequencedWorkerPool> pool) : enhanced_bookmark_model_(enhanced_bookmark_model), pool_(pool) { DCHECK(CalledOnValidThread()); // PersistentImageStore has to be constructed in the thread it will be used, // so we are posting the construction to the thread. However, we first // allocate memory and keep here. The reason is that, before // PersistentImageStore construction is done, it's possible that // another member function, that posts store_ to the thread, is called. // Although the construction might not be finished yet, we still want to post // the task since it's guaranteed to be constructed by the time it is used, by // the sequential thread task pool. // // Other alternatives: // - Using a lock or WaitableEvent for PersistentImageStore construction. // But waiting on UI thread is discouraged. // - Posting the current BookmarkImageService instance instead of store_. // But this will require using a weak pointer and can potentially block // destroying BookmarkImageService. PersistentImageStore* store = (PersistentImageStore*)::operator new(sizeof(PersistentImageStore)); store_.reset(store); pool_->PostNamedSequencedWorkerTask( kSequenceToken, FROM_HERE, base::Bind(&ConstructPersistentImageStore, store, path)); } BookmarkImageService::~BookmarkImageService() { DCHECK(CalledOnValidThread()); pool_->PostNamedSequencedWorkerTask( kSequenceToken, FROM_HERE, base::Bind(&DeleteImageStore, store_.release())); } void BookmarkImageService::Shutdown() { DCHECK(CalledOnValidThread()); enhanced_bookmark_model_->bookmark_model()->RemoveObserver(this); enhanced_bookmark_model_ = NULL; } void BookmarkImageService::SalientImageForUrl(const GURL& page_url, Callback callback) { DCHECK(CalledOnValidThread()); SalientImageForUrl(page_url, true, callback); } void BookmarkImageService::RetrieveImageFromStore( const GURL& page_url, BookmarkImageService::Callback callback) { DCHECK(CalledOnValidThread()); pool_->PostSequencedWorkerTaskWithShutdownBehavior( pool_->GetNamedSequenceToken(kSequenceToken), FROM_HERE, base::Bind(&RetrieveImageFromStoreRelay, base::Unretained(store_.get()), page_url, callback, base::ThreadTaskRunnerHandle::Get()), base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); } void BookmarkImageService::RetrieveSalientImageForPageUrl( const GURL& page_url) { DCHECK(CalledOnValidThread()); if (IsPageUrlInProgress(page_url)) return; // A request for this URL is already in progress. in_progress_page_urls_.insert(page_url); const BookmarkNode* bookmark = enhanced_bookmark_model_->bookmark_model() ->GetMostRecentlyAddedUserNodeForURL(page_url); GURL image_url; if (bookmark) { int width; int height; enhanced_bookmark_model_->GetThumbnailImage( bookmark, &image_url, &width, &height); } RetrieveSalientImage( page_url, image_url, "", net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE, false); } void BookmarkImageService::FetchCallback(const GURL& page_url, Callback original_callback, const gfx::Image& image, const GURL& image_url) { DCHECK(CalledOnValidThread()); if (!image.IsEmpty() || !image_url.is_empty()) { // Either the image was in the store or there is no image in the store, but // an URL for an image is present, indicating that a previous attempt to // download the image failed. Just return the image. original_callback.Run(image, image_url); } else { // There is no image in the store, and no previous attempts to retrieve // one. Start a request to retrieve a salient image if there is an image // url set on a bookmark, and then enqueue the request for the image to // be triggered when the retrieval is finished. RetrieveSalientImageForPageUrl(page_url); SalientImageForUrl(page_url, false, original_callback); } } void BookmarkImageService::SalientImageForUrl(const GURL& page_url, bool fetch_from_bookmark, Callback callback) { DCHECK(CalledOnValidThread()); // If the request is done while the image is currently being retrieved, just // store the appropriate callbacks to call once the image is retrieved. if (IsPageUrlInProgress(page_url)) { callbacks_[page_url].push_back(callback); return; } if (!fetch_from_bookmark) { RetrieveImageFromStore(page_url, callback); } else { RetrieveImageFromStore(page_url, base::Bind(&BookmarkImageService::FetchCallback, base::Unretained(this), page_url, callback)); } } void BookmarkImageService::ProcessNewImage(const GURL& page_url, bool update_bookmarks, const gfx::Image& image, const GURL& image_url) { DCHECK(CalledOnValidThread()); StoreImage(image, image_url, page_url); in_progress_page_urls_.erase(page_url); ProcessRequests(page_url, image, image_url); if (update_bookmarks && image_url.is_valid()) { const BookmarkNode* bookmark = enhanced_bookmark_model_->bookmark_model() ->GetMostRecentlyAddedUserNodeForURL(page_url); if (bookmark) { const gfx::Size& size = image.Size(); bool result = enhanced_bookmark_model_->SetOriginalImage( bookmark, image_url, size.width(), size.height()); DCHECK(result); } } } bool BookmarkImageService::IsPageUrlInProgress(const GURL& page_url) { DCHECK(CalledOnValidThread()); return in_progress_page_urls_.find(page_url) != in_progress_page_urls_.end(); } void BookmarkImageService::StoreImage(const gfx::Image& image, const GURL& image_url, const GURL& page_url) { DCHECK(CalledOnValidThread()); if (!image.IsEmpty()) { pool_->PostNamedSequencedWorkerTask( kSequenceToken, FROM_HERE, base::Bind(&ImageStore::Insert, base::Unretained(store_.get()), page_url, image_url, image)); } } void BookmarkImageService::RemoveImageForUrl(const GURL& page_url) { DCHECK(CalledOnValidThread()); pool_->PostNamedSequencedWorkerTask( kSequenceToken, FROM_HERE, base::Bind(&ImageStore::Erase, base::Unretained(store_.get()), page_url)); in_progress_page_urls_.erase(page_url); ProcessRequests(page_url, gfx::Image(), GURL()); } void BookmarkImageService::ChangeImageURL(const GURL& from, const GURL& to) { DCHECK(CalledOnValidThread()); pool_->PostNamedSequencedWorkerTask(kSequenceToken, FROM_HERE, base::Bind(&ImageStore::ChangeImageURL, base::Unretained(store_.get()), from, to)); in_progress_page_urls_.erase(from); ProcessRequests(from, gfx::Image(), GURL()); } void BookmarkImageService::ClearAll() { DCHECK(CalledOnValidThread()); // Clears and executes callbacks. pool_->PostNamedSequencedWorkerTask( kSequenceToken, FROM_HERE, base::Bind(&ImageStore::ClearAll, base::Unretained(store_.get()))); for (std::map<const GURL, std::vector<Callback> >::const_iterator it = callbacks_.begin(); it != callbacks_.end(); ++it) { ProcessRequests(it->first, gfx::Image(), GURL()); } in_progress_page_urls_.erase(in_progress_page_urls_.begin(), in_progress_page_urls_.end()); } void BookmarkImageService::ProcessRequests(const GURL& page_url, const gfx::Image& image, const GURL& image_url) { DCHECK(CalledOnValidThread()); std::vector<Callback> callbacks = callbacks_[page_url]; for (std::vector<Callback>::const_iterator it = callbacks.begin(); it != callbacks.end(); ++it) { it->Run(image, image_url); } callbacks_.erase(page_url); } // BookmarkModelObserver methods. void BookmarkImageService::BookmarkNodeRemoved( BookmarkModel* model, const BookmarkNode* parent, int old_index, const BookmarkNode* node, const std::set<GURL>& removed_urls) { DCHECK(CalledOnValidThread()); for (std::set<GURL>::const_iterator iter = removed_urls.begin(); iter != removed_urls.end(); ++iter) { RemoveImageForUrl(*iter); } } void BookmarkImageService::BookmarkModelLoaded(BookmarkModel* model, bool ids_reassigned) { } void BookmarkImageService::BookmarkNodeMoved(BookmarkModel* model, const BookmarkNode* old_parent, int old_index, const BookmarkNode* new_parent, int new_index) { } void BookmarkImageService::BookmarkNodeAdded(BookmarkModel* model, const BookmarkNode* parent, int index) { } void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel* model, const BookmarkNode* node) { DCHECK(CalledOnValidThread()); if (node->is_url()) previous_url_ = node->url(); } void BookmarkImageService::BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node) { DCHECK(CalledOnValidThread()); if (node->is_url() && previous_url_ != node->url()) ChangeImageURL(previous_url_, node->url()); } void BookmarkImageService::BookmarkNodeFaviconChanged( BookmarkModel* model, const BookmarkNode* node) { } void BookmarkImageService::BookmarkNodeChildrenReordered( BookmarkModel* model, const BookmarkNode* node) { } void BookmarkImageService::BookmarkAllUserNodesRemoved( BookmarkModel* model, const std::set<GURL>& removed_urls) { ClearAll(); } } // namespace enhanced_bookmarks