blob: 066d9f93caedfa85dca7f347577069bf1968f6d6 [file] [log] [blame]
// 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