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"
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"
17 const char kSequenceToken[] = "BookmarkImagesSequenceToken";
19 void ConstructPersistentImageStore(PersistentImageStore* store,
20 const base::FilePath& path) {
22 new (store) PersistentImageStore(path);
25 void DeleteImageStore(ImageStore* store) {
30 void RetrieveImageFromStoreRelay(
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));
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),
50 DCHECK(CalledOnValidThread());
51 enhanced_bookmark_model_->bookmark_model()->AddObserver(this);
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.
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));
78 pool_->PostNamedSequencedWorkerTask(
81 base::Bind(&ConstructPersistentImageStore, store, path));
84 BookmarkImageService::~BookmarkImageService() {
85 DCHECK(CalledOnValidThread());
86 pool_->PostNamedSequencedWorkerTask(
89 base::Bind(&DeleteImageStore, store_.release()));
92 void BookmarkImageService::Shutdown() {
93 DCHECK(CalledOnValidThread());
94 enhanced_bookmark_model_->bookmark_model()->RemoveObserver(this);
95 enhanced_bookmark_model_ = NULL;
98 void BookmarkImageService::SalientImageForUrl(const GURL& page_url,
100 DCHECK(CalledOnValidThread());
101 SalientImageForUrl(page_url, true, callback);
104 void BookmarkImageService::RetrieveImageFromStore(
105 const GURL& page_url,
106 BookmarkImageService::Callback callback) {
107 DCHECK(CalledOnValidThread());
108 pool_->PostSequencedWorkerTaskWithShutdownBehavior(
109 pool_->GetNamedSequenceToken(kSequenceToken),
111 base::Bind(&RetrieveImageFromStoreRelay,
112 base::Unretained(store_.get()),
115 base::ThreadTaskRunnerHandle::Get()),
116 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
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.
125 in_progress_page_urls_.insert(page_url);
127 const BookmarkNode* bookmark =
128 enhanced_bookmark_model_->bookmark_model()
129 ->GetMostRecentlyAddedUserNodeForURL(page_url);
134 enhanced_bookmark_model_->GetThumbnailImage(
135 bookmark, &image_url, &width, &height);
138 RetrieveSalientImage(
142 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
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);
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);
166 void BookmarkImageService::SalientImageForUrl(const GURL& page_url,
167 bool fetch_from_bookmark,
169 DCHECK(CalledOnValidThread());
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);
178 if (!fetch_from_bookmark) {
179 RetrieveImageFromStore(page_url, callback);
181 RetrieveImageFromStore(page_url,
182 base::Bind(&BookmarkImageService::FetchCallback,
183 base::Unretained(this),
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);
202 const gfx::Size& size = image.Size();
203 bool result = enhanced_bookmark_model_->SetOriginalImage(
204 bookmark, image_url, size.width(), size.height());
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();
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(
223 base::Bind(&ImageStore::Insert,
224 base::Unretained(store_.get()),
231 void BookmarkImageService::RemoveImageForUrl(const GURL& page_url) {
232 DCHECK(CalledOnValidThread());
233 pool_->PostNamedSequencedWorkerTask(
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());
241 void BookmarkImageService::ChangeImageURL(const GURL& from, const GURL& to) {
242 DCHECK(CalledOnValidThread());
243 pool_->PostNamedSequencedWorkerTask(kSequenceToken,
245 base::Bind(&ImageStore::ChangeImageURL,
246 base::Unretained(store_.get()),
249 in_progress_page_urls_.erase(from);
250 ProcessRequests(from, gfx::Image(), GURL());
253 void BookmarkImageService::ClearAll() {
254 DCHECK(CalledOnValidThread());
255 // Clears and executes callbacks.
256 pool_->PostNamedSequencedWorkerTask(
259 base::Bind(&ImageStore::ClearAll, base::Unretained(store_.get())));
261 for (std::map<const GURL, std::vector<Callback> >::const_iterator it =
263 it != callbacks_.end();
265 ProcessRequests(it->first, gfx::Image(), GURL());
268 in_progress_page_urls_.erase(in_progress_page_urls_.begin(),
269 in_progress_page_urls_.end());
272 void BookmarkImageService::ProcessRequests(const GURL& page_url,
273 const gfx::Image& image,
274 const GURL& image_url) {
275 DCHECK(CalledOnValidThread());
277 std::vector<Callback> callbacks = callbacks_[page_url];
278 for (std::vector<Callback>::const_iterator it = callbacks.begin();
279 it != callbacks.end();
281 it->Run(image, image_url);
284 callbacks_.erase(page_url);
287 // BookmarkModelObserver methods.
289 void BookmarkImageService::BookmarkNodeRemoved(
290 BookmarkModel* model,
291 const BookmarkNode* parent,
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();
299 RemoveImageForUrl(*iter);
303 void BookmarkImageService::BookmarkModelLoaded(BookmarkModel* model,
304 bool ids_reassigned) {
307 void BookmarkImageService::BookmarkNodeMoved(BookmarkModel* model,
308 const BookmarkNode* old_parent,
310 const BookmarkNode* new_parent,
314 void BookmarkImageService::BookmarkNodeAdded(BookmarkModel* model,
315 const BookmarkNode* parent,
319 void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel* model,
320 const BookmarkNode* node) {
321 DCHECK(CalledOnValidThread());
323 previous_url_ = node->url();
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());
333 void BookmarkImageService::BookmarkNodeFaviconChanged(
334 BookmarkModel* model,
335 const BookmarkNode* node) {
338 void BookmarkImageService::BookmarkNodeChildrenReordered(
339 BookmarkModel* model,
340 const BookmarkNode* node) {
343 void BookmarkImageService::BookmarkAllUserNodesRemoved(
344 BookmarkModel* model,
345 const std::set<GURL>& removed_urls) {
349 } // namespace enhanced_bookmarks