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.
5 #include "components/enhanced_bookmarks/bookmark_image_service.h"
7 #include "base/single_thread_task_runner.h"
8 #include "base/task_runner_util.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "components/bookmarks/browser/bookmark_model.h"
12 #include "components/bookmarks/browser/bookmark_model_observer.h"
13 #include "components/enhanced_bookmarks/enhanced_bookmark_model.h"
14 #include "components/enhanced_bookmarks/enhanced_bookmark_utils.h"
15 #include "components/enhanced_bookmarks/image_store_util.h"
16 #include "components/enhanced_bookmarks/persistent_image_store.h"
18 using bookmarks::BookmarkModel
;
19 using bookmarks::BookmarkNode
;
23 const char kSequenceToken
[] = "BookmarkImagesSequenceToken";
25 void ConstructPersistentImageStore(PersistentImageStore
* store
,
26 const base::FilePath
& path
) {
28 new (store
) PersistentImageStore(path
);
31 void DeleteImageStore(ImageStore
* store
) {
36 void RetrieveImageFromStoreRelay(
39 enhanced_bookmarks::BookmarkImageService::ImageCallback callback
,
40 scoped_refptr
<base::SingleThreadTaskRunner
> origin_loop
) {
41 origin_loop
->PostTask(FROM_HERE
, base::Bind(callback
, store
->Get(page_url
)));
46 namespace enhanced_bookmarks
{
47 BookmarkImageService::BookmarkImageService(
48 scoped_ptr
<ImageStore
> store
,
49 EnhancedBookmarkModel
* enhanced_bookmark_model
,
50 scoped_refptr
<base::SequencedWorkerPool
> pool
)
51 : enhanced_bookmark_model_(enhanced_bookmark_model
),
54 DCHECK(CalledOnValidThread());
55 enhanced_bookmark_model_
->bookmark_model()->AddObserver(this);
58 BookmarkImageService::BookmarkImageService(
59 const base::FilePath
& path
,
60 EnhancedBookmarkModel
* enhanced_bookmark_model
,
61 scoped_refptr
<base::SequencedWorkerPool
> pool
)
62 : enhanced_bookmark_model_(enhanced_bookmark_model
), pool_(pool
) {
63 DCHECK(CalledOnValidThread());
64 // PersistentImageStore has to be constructed in the thread it will be used,
65 // so we are posting the construction to the thread. However, we first
66 // allocate memory and keep here. The reason is that, before
67 // PersistentImageStore construction is done, it's possible that
68 // another member function, that posts store_ to the thread, is called.
69 // Although the construction might not be finished yet, we still want to post
70 // the task since it's guaranteed to be constructed by the time it is used, by
71 // the sequential thread task pool.
73 // Other alternatives:
74 // - Using a lock or WaitableEvent for PersistentImageStore construction.
75 // But waiting on UI thread is discouraged.
76 // - Posting the current BookmarkImageService instance instead of store_.
77 // But this will require using a weak pointer and can potentially block
78 // destroying BookmarkImageService.
79 PersistentImageStore
* store
=
80 (PersistentImageStore
*)::operator new(sizeof(PersistentImageStore
));
82 pool_
->PostNamedSequencedWorkerTask(
85 base::Bind(&ConstructPersistentImageStore
, store
, path
));
88 BookmarkImageService::~BookmarkImageService() {
89 DCHECK(CalledOnValidThread());
90 pool_
->PostNamedSequencedWorkerTask(
93 base::Bind(&DeleteImageStore
, store_
.release()));
96 void BookmarkImageService::Shutdown() {
97 DCHECK(CalledOnValidThread());
98 enhanced_bookmark_model_
->bookmark_model()->RemoveObserver(this);
99 enhanced_bookmark_model_
= NULL
;
102 void BookmarkImageService::SalientImageForUrl(const GURL
& page_url
,
103 ImageCallback callback
) {
104 DCHECK(CalledOnValidThread());
105 SalientImageForUrl(page_url
, true, callback
);
108 void BookmarkImageService::RetrieveImageFromStore(const GURL
& page_url
,
109 ImageCallback callback
) {
110 DCHECK(CalledOnValidThread());
111 pool_
->PostSequencedWorkerTaskWithShutdownBehavior(
112 pool_
->GetNamedSequenceToken(kSequenceToken
),
114 base::Bind(&RetrieveImageFromStoreRelay
,
115 base::Unretained(store_
.get()),
118 base::ThreadTaskRunnerHandle::Get()),
119 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
122 void BookmarkImageService::RetrieveSalientImageForPageUrl(
123 const GURL
& page_url
) {
124 DCHECK(CalledOnValidThread());
125 if (IsPageUrlInProgress(page_url
))
126 return; // A request for this URL is already in progress.
128 in_progress_page_urls_
.insert(page_url
);
130 const BookmarkNode
* bookmark
=
131 enhanced_bookmark_model_
->bookmark_model()
132 ->GetMostRecentlyAddedUserNodeForURL(page_url
);
137 enhanced_bookmark_model_
->GetThumbnailImage(
138 bookmark
, &image_url
, &width
, &height
);
141 RetrieveSalientImage(
145 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE
,
149 void BookmarkImageService::FetchCallback(const GURL
& page_url
,
150 ImageCallback original_callback
,
151 scoped_refptr
<ImageRecord
> record
) {
152 DCHECK(CalledOnValidThread());
153 if (!record
->image
->IsEmpty() || !record
->url
.is_empty()) {
154 // Either the record was in the store or there is no image in the store, but
155 // an URL for a record is present, indicating that a previous attempt to
156 // download the image failed. Just return the record.
157 original_callback
.Run(record
);
159 // There is no record in the store, and no previous attempts to retrieve
160 // one. Start a request to retrieve a salient image if there is an image
161 // url set on a bookmark, and then enqueue the request for the record to
162 // be triggered when the retrieval is finished.
163 RetrieveSalientImageForPageUrl(page_url
);
164 SalientImageForUrl(page_url
, false, original_callback
);
168 void BookmarkImageService::SalientImageForUrl(const GURL
& page_url
,
170 ImageCallback callback
) {
171 DCHECK(CalledOnValidThread());
173 // If the request is done while the image is currently being retrieved, just
174 // store the appropriate callbacks to call once the image is retrieved.
175 if (IsPageUrlInProgress(page_url
)) {
176 callbacks_
[page_url
].push_back(callback
);
180 if (!fetch_from_web
) {
181 RetrieveImageFromStore(page_url
, callback
);
183 RetrieveImageFromStore(page_url
,
184 base::Bind(&BookmarkImageService::FetchCallback
,
185 base::Unretained(this),
191 void BookmarkImageService::ProcessNewImage(const GURL
& page_url
,
192 bool update_bookmarks
,
193 const GURL
& image_url
,
194 scoped_ptr
<gfx::Image
> image
) {
195 DCHECK(CalledOnValidThread());
197 gfx::Size size
= image
->Size();
198 PostTaskToStoreImage(image
.Pass(), image_url
, page_url
);
200 if (update_bookmarks
&& image_url
.is_valid()) {
201 const BookmarkNode
* bookmark
=
202 enhanced_bookmark_model_
->bookmark_model()
203 ->GetMostRecentlyAddedUserNodeForURL(page_url
);
206 bool result
= enhanced_bookmark_model_
->SetOriginalImage(
207 bookmark
, image_url
, size
.width(), size
.height());
213 bool BookmarkImageService::IsPageUrlInProgress(const GURL
& page_url
) {
214 DCHECK(CalledOnValidThread());
215 return in_progress_page_urls_
.find(page_url
) != in_progress_page_urls_
.end();
218 scoped_refptr
<ImageRecord
> BookmarkImageService::ResizeAndStoreImage(
219 scoped_refptr
<ImageRecord
> image_info
,
220 const GURL
& page_url
) {
222 if (!image_info
->image
->IsEmpty()) {
223 image_info
->image
= ResizeImage(*image_info
->image
);
224 image_info
->dominant_color
= DominantColorForImage(*image_info
->image
);
225 // TODO(lpromero): this should be saved all the time, even when there is an
226 // empty image. http://crbug.com/451450
227 pool_
->PostNamedSequencedWorkerTask(
228 kSequenceToken
, FROM_HERE
,
229 base::Bind(&ImageStore::Insert
, base::Unretained(store_
.get()),
230 page_url
, image_info
));
236 void BookmarkImageService::PostTaskToStoreImage(
237 scoped_ptr
<gfx::Image
> image
,
238 const GURL
& image_url
,
239 const GURL
& page_url
) {
240 DCHECK(CalledOnValidThread());
242 scoped_refptr
<ImageRecord
> image_info(
243 new ImageRecord(image
.Pass(), image_url
));
245 // TODO ensure image thread safety.
246 base::Callback
<scoped_refptr
<ImageRecord
>(void)> task
=
247 base::Bind(&BookmarkImageService::ResizeAndStoreImage
,
248 base::Unretained(this), image_info
, page_url
);
249 base::Callback
<void(scoped_refptr
<ImageRecord
>)> reply
=
250 base::Bind(&BookmarkImageService::OnStoreImagePosted
,
251 base::Unretained(this), page_url
);
253 base::PostTaskAndReplyWithResult(pool_
.get(), FROM_HERE
, task
, reply
);
256 void BookmarkImageService::OnStoreImagePosted(
257 const GURL
& page_url
,
258 scoped_refptr
<ImageRecord
> image
) {
259 DCHECK(CalledOnValidThread());
260 in_progress_page_urls_
.erase(page_url
);
261 ProcessRequests(page_url
, image
);
264 void BookmarkImageService::RemoveImageForUrl(const GURL
& page_url
) {
265 DCHECK(CalledOnValidThread());
266 pool_
->PostNamedSequencedWorkerTask(
269 base::Bind(&ImageStore::Erase
, base::Unretained(store_
.get()), page_url
));
270 in_progress_page_urls_
.erase(page_url
);
271 ProcessRequests(page_url
, scoped_refptr
<ImageRecord
>(new ImageRecord()));
274 void BookmarkImageService::ChangeImageURL(const GURL
& from
, const GURL
& to
) {
275 DCHECK(CalledOnValidThread());
276 pool_
->PostNamedSequencedWorkerTask(kSequenceToken
,
278 base::Bind(&ImageStore::ChangeImageURL
,
279 base::Unretained(store_
.get()),
282 in_progress_page_urls_
.erase(from
);
283 ProcessRequests(from
, scoped_refptr
<ImageRecord
>(new ImageRecord()));
286 void BookmarkImageService::ClearAll() {
287 DCHECK(CalledOnValidThread());
288 // Clears and executes callbacks.
289 pool_
->PostNamedSequencedWorkerTask(
292 base::Bind(&ImageStore::ClearAll
, base::Unretained(store_
.get())));
294 for (std::map
<const GURL
, std::vector
<ImageCallback
>>::const_iterator it
=
296 it
!= callbacks_
.end(); ++it
) {
297 ProcessRequests(it
->first
, scoped_refptr
<ImageRecord
>(new ImageRecord()));
300 in_progress_page_urls_
.erase(in_progress_page_urls_
.begin(),
301 in_progress_page_urls_
.end());
304 void BookmarkImageService::ProcessRequests(const GURL
& page_url
,
305 scoped_refptr
<ImageRecord
> record
) {
306 DCHECK(CalledOnValidThread());
308 std::vector
<ImageCallback
> callbacks
= callbacks_
[page_url
];
309 for (std::vector
<ImageCallback
>::const_iterator it
= callbacks
.begin();
310 it
!= callbacks
.end(); ++it
) {
314 callbacks_
.erase(page_url
);
317 // BookmarkModelObserver methods.
319 void BookmarkImageService::BookmarkNodeRemoved(
320 BookmarkModel
* model
,
321 const BookmarkNode
* parent
,
323 const BookmarkNode
* node
,
324 const std::set
<GURL
>& removed_urls
) {
325 DCHECK(CalledOnValidThread());
326 for (std::set
<GURL
>::const_iterator iter
= removed_urls
.begin();
327 iter
!= removed_urls
.end();
329 RemoveImageForUrl(*iter
);
333 void BookmarkImageService::BookmarkModelLoaded(BookmarkModel
* model
,
334 bool ids_reassigned
) {
337 void BookmarkImageService::BookmarkNodeMoved(BookmarkModel
* model
,
338 const BookmarkNode
* old_parent
,
340 const BookmarkNode
* new_parent
,
344 void BookmarkImageService::BookmarkNodeAdded(BookmarkModel
* model
,
345 const BookmarkNode
* parent
,
349 void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel
* model
,
350 const BookmarkNode
* node
) {
351 DCHECK(CalledOnValidThread());
353 previous_url_
= node
->url();
356 void BookmarkImageService::BookmarkNodeChanged(BookmarkModel
* model
,
357 const BookmarkNode
* node
) {
358 DCHECK(CalledOnValidThread());
359 if (node
->is_url() && previous_url_
!= node
->url())
360 ChangeImageURL(previous_url_
, node
->url());
363 void BookmarkImageService::BookmarkNodeFaviconChanged(
364 BookmarkModel
* model
,
365 const BookmarkNode
* node
) {
368 void BookmarkImageService::BookmarkNodeChildrenReordered(
369 BookmarkModel
* model
,
370 const BookmarkNode
* node
) {
373 void BookmarkImageService::BookmarkAllUserNodesRemoved(
374 BookmarkModel
* model
,
375 const std::set
<GURL
>& removed_urls
) {
379 } // namespace enhanced_bookmarks