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 const 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 const gfx::Image
& image
) {
195 DCHECK(CalledOnValidThread());
196 PostTaskToStoreImage(image
, image_url
, page_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 ImageRecord
BookmarkImageService::ResizeAndStoreImage(const gfx::Image
& image
,
216 const GURL
& image_url
,
217 const GURL
& page_url
) {
218 gfx::Image resized_image
= ResizeImage(image
);
219 ImageRecord
image_info(resized_image
, image_url
, SK_ColorBLACK
);
220 if (!resized_image
.IsEmpty()) {
221 image_info
.dominant_color
= DominantColorForImage(resized_image
);
222 // TODO(lpromero): this should be saved all the time, even when there is an
223 // empty image. http://crbug.com/451450
224 pool_
->PostNamedSequencedWorkerTask(
225 kSequenceToken
, FROM_HERE
,
226 base::Bind(&ImageStore::Insert
, base::Unretained(store_
.get()),
227 page_url
, image_info
));
232 void BookmarkImageService::PostTaskToStoreImage(const gfx::Image
& image
,
233 const GURL
& image_url
,
234 const GURL
& page_url
) {
235 DCHECK(CalledOnValidThread());
237 base::Callback
<ImageRecord(void)> task
=
238 base::Bind(&BookmarkImageService::ResizeAndStoreImage
,
239 base::Unretained(this), image
, image_url
, page_url
);
240 base::Callback
<void(const ImageRecord
&)> reply
=
241 base::Bind(&BookmarkImageService::OnStoreImagePosted
,
242 base::Unretained(this), page_url
);
244 base::PostTaskAndReplyWithResult(pool_
.get(), FROM_HERE
, task
, reply
);
247 void BookmarkImageService::OnStoreImagePosted(const GURL
& page_url
,
248 const ImageRecord
& image
) {
249 DCHECK(CalledOnValidThread());
250 in_progress_page_urls_
.erase(page_url
);
251 ProcessRequests(page_url
, image
);
254 void BookmarkImageService::RemoveImageForUrl(const GURL
& page_url
) {
255 DCHECK(CalledOnValidThread());
256 pool_
->PostNamedSequencedWorkerTask(
259 base::Bind(&ImageStore::Erase
, base::Unretained(store_
.get()), page_url
));
260 in_progress_page_urls_
.erase(page_url
);
261 ProcessRequests(page_url
, ImageRecord());
264 void BookmarkImageService::ChangeImageURL(const GURL
& from
, const GURL
& to
) {
265 DCHECK(CalledOnValidThread());
266 pool_
->PostNamedSequencedWorkerTask(kSequenceToken
,
268 base::Bind(&ImageStore::ChangeImageURL
,
269 base::Unretained(store_
.get()),
272 in_progress_page_urls_
.erase(from
);
273 ProcessRequests(from
, ImageRecord());
276 void BookmarkImageService::ClearAll() {
277 DCHECK(CalledOnValidThread());
278 // Clears and executes callbacks.
279 pool_
->PostNamedSequencedWorkerTask(
282 base::Bind(&ImageStore::ClearAll
, base::Unretained(store_
.get())));
284 for (std::map
<const GURL
, std::vector
<ImageCallback
>>::const_iterator it
=
286 it
!= callbacks_
.end(); ++it
) {
287 ProcessRequests(it
->first
, ImageRecord());
290 in_progress_page_urls_
.erase(in_progress_page_urls_
.begin(),
291 in_progress_page_urls_
.end());
294 void BookmarkImageService::ProcessRequests(const GURL
& page_url
,
295 const ImageRecord
& record
) {
296 DCHECK(CalledOnValidThread());
298 std::vector
<ImageCallback
> callbacks
= callbacks_
[page_url
];
299 for (std::vector
<ImageCallback
>::const_iterator it
= callbacks
.begin();
300 it
!= callbacks
.end(); ++it
) {
304 callbacks_
.erase(page_url
);
307 // BookmarkModelObserver methods.
309 void BookmarkImageService::BookmarkNodeRemoved(
310 BookmarkModel
* model
,
311 const BookmarkNode
* parent
,
313 const BookmarkNode
* node
,
314 const std::set
<GURL
>& removed_urls
) {
315 DCHECK(CalledOnValidThread());
316 for (std::set
<GURL
>::const_iterator iter
= removed_urls
.begin();
317 iter
!= removed_urls
.end();
319 RemoveImageForUrl(*iter
);
323 void BookmarkImageService::BookmarkModelLoaded(BookmarkModel
* model
,
324 bool ids_reassigned
) {
327 void BookmarkImageService::BookmarkNodeMoved(BookmarkModel
* model
,
328 const BookmarkNode
* old_parent
,
330 const BookmarkNode
* new_parent
,
334 void BookmarkImageService::BookmarkNodeAdded(BookmarkModel
* model
,
335 const BookmarkNode
* parent
,
339 void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel
* model
,
340 const BookmarkNode
* node
) {
341 DCHECK(CalledOnValidThread());
343 previous_url_
= node
->url();
346 void BookmarkImageService::BookmarkNodeChanged(BookmarkModel
* model
,
347 const BookmarkNode
* node
) {
348 DCHECK(CalledOnValidThread());
349 if (node
->is_url() && previous_url_
!= node
->url())
350 ChangeImageURL(previous_url_
, node
->url());
353 void BookmarkImageService::BookmarkNodeFaviconChanged(
354 BookmarkModel
* model
,
355 const BookmarkNode
* node
) {
358 void BookmarkImageService::BookmarkNodeChildrenReordered(
359 BookmarkModel
* model
,
360 const BookmarkNode
* node
) {
363 void BookmarkImageService::BookmarkAllUserNodesRemoved(
364 BookmarkModel
* model
,
365 const std::set
<GURL
>& removed_urls
) {
369 } // namespace enhanced_bookmarks