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
;
22 const char kSequenceToken
[] = "BookmarkImagesSequenceToken";
24 void ConstructPersistentImageStore(PersistentImageStore
* store
,
25 const base::FilePath
& path
) {
27 new (store
) PersistentImageStore(path
);
30 void DeleteImageStore(ImageStore
* store
) {
35 void RetrieveImageFromStoreRelay(
38 enhanced_bookmarks::BookmarkImageService::ImageCallback callback
,
39 scoped_refptr
<base::SingleThreadTaskRunner
> origin_loop
) {
40 origin_loop
->PostTask(FROM_HERE
, base::Bind(callback
, store
->Get(page_url
)));
45 namespace enhanced_bookmarks
{
46 BookmarkImageService::BookmarkImageService(
47 scoped_ptr
<ImageStore
> store
,
48 EnhancedBookmarkModel
* enhanced_bookmark_model
,
49 scoped_refptr
<base::SequencedWorkerPool
> pool
)
50 : enhanced_bookmark_model_(enhanced_bookmark_model
),
53 DCHECK(CalledOnValidThread());
54 enhanced_bookmark_model_
->bookmark_model()->AddObserver(this);
57 BookmarkImageService::BookmarkImageService(
58 const base::FilePath
& path
,
59 EnhancedBookmarkModel
* enhanced_bookmark_model
,
60 scoped_refptr
<base::SequencedWorkerPool
> pool
)
61 : enhanced_bookmark_model_(enhanced_bookmark_model
), pool_(pool
) {
62 DCHECK(CalledOnValidThread());
63 // PersistentImageStore has to be constructed in the thread it will be used,
64 // so we are posting the construction to the thread. However, we first
65 // allocate memory and keep here. The reason is that, before
66 // PersistentImageStore construction is done, it's possible that
67 // another member function, that posts store_ to the thread, is called.
68 // Although the construction might not be finished yet, we still want to post
69 // the task since it's guaranteed to be constructed by the time it is used, by
70 // the sequential thread task pool.
72 // Other alternatives:
73 // - Using a lock or WaitableEvent for PersistentImageStore construction.
74 // But waiting on UI thread is discouraged.
75 // - Posting the current BookmarkImageService instance instead of store_.
76 // But this will require using a weak pointer and can potentially block
77 // destroying BookmarkImageService.
78 PersistentImageStore
* store
=
79 (PersistentImageStore
*)::operator new(sizeof(PersistentImageStore
));
81 pool_
->PostNamedSequencedWorkerTask(
84 base::Bind(&ConstructPersistentImageStore
, store
, path
));
87 BookmarkImageService::~BookmarkImageService() {
88 DCHECK(CalledOnValidThread());
89 pool_
->PostNamedSequencedWorkerTask(
92 base::Bind(&DeleteImageStore
, store_
.release()));
95 void BookmarkImageService::Shutdown() {
96 DCHECK(CalledOnValidThread());
97 enhanced_bookmark_model_
->bookmark_model()->RemoveObserver(this);
98 enhanced_bookmark_model_
= NULL
;
101 void BookmarkImageService::SalientImageForUrl(const GURL
& page_url
,
102 ImageCallback callback
) {
103 DCHECK(CalledOnValidThread());
104 SalientImageForUrl(page_url
, true, callback
);
107 void BookmarkImageService::RetrieveImageFromStore(const GURL
& page_url
,
108 ImageCallback callback
) {
109 DCHECK(CalledOnValidThread());
110 pool_
->PostSequencedWorkerTaskWithShutdownBehavior(
111 pool_
->GetNamedSequenceToken(kSequenceToken
),
113 base::Bind(&RetrieveImageFromStoreRelay
,
114 base::Unretained(store_
.get()),
117 base::ThreadTaskRunnerHandle::Get()),
118 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
121 void BookmarkImageService::RetrieveSalientImageForPageUrl(
122 const GURL
& page_url
) {
123 DCHECK(CalledOnValidThread());
124 if (IsPageUrlInProgress(page_url
))
125 return; // A request for this URL is already in progress.
127 in_progress_page_urls_
.insert(page_url
);
129 const BookmarkNode
* bookmark
=
130 enhanced_bookmark_model_
->bookmark_model()
131 ->GetMostRecentlyAddedUserNodeForURL(page_url
);
136 enhanced_bookmark_model_
->GetThumbnailImage(
137 bookmark
, &image_url
, &width
, &height
);
140 RetrieveSalientImage(
144 net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE
,
148 void BookmarkImageService::FetchCallback(const GURL
& page_url
,
149 ImageCallback original_callback
,
150 const ImageRecord
& record
) {
151 DCHECK(CalledOnValidThread());
152 if (!record
.image
.IsEmpty() || !record
.url
.is_empty()) {
153 // Either the record was in the store or there is no image in the store, but
154 // an URL for a record is present, indicating that a previous attempt to
155 // download the image failed. Just return the record.
156 original_callback
.Run(record
);
158 // There is no record in the store, and no previous attempts to retrieve
159 // one. Start a request to retrieve a salient image if there is an image
160 // url set on a bookmark, and then enqueue the request for the record to
161 // be triggered when the retrieval is finished.
162 RetrieveSalientImageForPageUrl(page_url
);
163 SalientImageForUrl(page_url
, false, original_callback
);
167 void BookmarkImageService::SalientImageForUrl(const GURL
& page_url
,
168 bool fetch_from_bookmark
,
169 ImageCallback callback
) {
170 DCHECK(CalledOnValidThread());
172 // If the request is done while the image is currently being retrieved, just
173 // store the appropriate callbacks to call once the image is retrieved.
174 if (IsPageUrlInProgress(page_url
)) {
175 callbacks_
[page_url
].push_back(callback
);
179 if (!fetch_from_bookmark
) {
180 RetrieveImageFromStore(page_url
, callback
);
182 RetrieveImageFromStore(page_url
,
183 base::Bind(&BookmarkImageService::FetchCallback
,
184 base::Unretained(this),
190 void BookmarkImageService::ProcessNewImage(const GURL
& page_url
,
191 bool update_bookmarks
,
192 const gfx::Image
& image
,
193 const GURL
& image_url
) {
194 DCHECK(CalledOnValidThread());
195 PostTaskToStoreImage(image
, image_url
, page_url
);
196 if (update_bookmarks
&& image_url
.is_valid()) {
197 const BookmarkNode
* bookmark
=
198 enhanced_bookmark_model_
->bookmark_model()
199 ->GetMostRecentlyAddedUserNodeForURL(page_url
);
201 const gfx::Size
& size
= image
.Size();
202 bool result
= enhanced_bookmark_model_
->SetOriginalImage(
203 bookmark
, image_url
, size
.width(), size
.height());
209 bool BookmarkImageService::IsPageUrlInProgress(const GURL
& page_url
) {
210 DCHECK(CalledOnValidThread());
211 return in_progress_page_urls_
.find(page_url
) != in_progress_page_urls_
.end();
214 ImageRecord
BookmarkImageService::StoreImage(const gfx::Image
& image
,
215 const GURL
& image_url
,
216 const GURL
& page_url
) {
217 ImageRecord
image_info(image
, image_url
, SK_ColorBLACK
);
218 if (!image
.IsEmpty()) {
219 image_info
.dominant_color
= DominantColorForImage(image
);
220 // TODO(lpromero): this should be saved all the time, even when there is an
221 // empty image. http://crbug.com/451450
222 pool_
->PostNamedSequencedWorkerTask(
223 kSequenceToken
, FROM_HERE
,
224 base::Bind(&ImageStore::Insert
, base::Unretained(store_
.get()),
225 page_url
, image_info
));
230 void BookmarkImageService::PostTaskToStoreImage(const gfx::Image
& image
,
231 const GURL
& image_url
,
232 const GURL
& page_url
) {
233 DCHECK(CalledOnValidThread());
235 base::Callback
<ImageRecord(void)> task
=
236 base::Bind(&BookmarkImageService::StoreImage
, base::Unretained(this),
237 image
, image_url
, page_url
);
238 base::Callback
<void(const ImageRecord
&)> reply
=
239 base::Bind(&BookmarkImageService::OnStoreImagePosted
,
240 base::Unretained(this), page_url
);
242 base::PostTaskAndReplyWithResult(pool_
.get(), FROM_HERE
, task
, reply
);
245 void BookmarkImageService::OnStoreImagePosted(const GURL
& page_url
,
246 const ImageRecord
& image
) {
247 DCHECK(CalledOnValidThread());
248 in_progress_page_urls_
.erase(page_url
);
249 ProcessRequests(page_url
, image
);
252 void BookmarkImageService::RemoveImageForUrl(const GURL
& page_url
) {
253 DCHECK(CalledOnValidThread());
254 pool_
->PostNamedSequencedWorkerTask(
257 base::Bind(&ImageStore::Erase
, base::Unretained(store_
.get()), page_url
));
258 in_progress_page_urls_
.erase(page_url
);
259 ProcessRequests(page_url
, ImageRecord());
262 void BookmarkImageService::ChangeImageURL(const GURL
& from
, const GURL
& to
) {
263 DCHECK(CalledOnValidThread());
264 pool_
->PostNamedSequencedWorkerTask(kSequenceToken
,
266 base::Bind(&ImageStore::ChangeImageURL
,
267 base::Unretained(store_
.get()),
270 in_progress_page_urls_
.erase(from
);
271 ProcessRequests(from
, ImageRecord());
274 void BookmarkImageService::ClearAll() {
275 DCHECK(CalledOnValidThread());
276 // Clears and executes callbacks.
277 pool_
->PostNamedSequencedWorkerTask(
280 base::Bind(&ImageStore::ClearAll
, base::Unretained(store_
.get())));
282 for (std::map
<const GURL
, std::vector
<ImageCallback
>>::const_iterator it
=
284 it
!= callbacks_
.end(); ++it
) {
285 ProcessRequests(it
->first
, ImageRecord());
288 in_progress_page_urls_
.erase(in_progress_page_urls_
.begin(),
289 in_progress_page_urls_
.end());
292 void BookmarkImageService::ProcessRequests(const GURL
& page_url
,
293 const ImageRecord
& record
) {
294 DCHECK(CalledOnValidThread());
296 std::vector
<ImageCallback
> callbacks
= callbacks_
[page_url
];
297 for (std::vector
<ImageCallback
>::const_iterator it
= callbacks
.begin();
298 it
!= callbacks
.end(); ++it
) {
302 callbacks_
.erase(page_url
);
305 // BookmarkModelObserver methods.
307 void BookmarkImageService::BookmarkNodeRemoved(
308 BookmarkModel
* model
,
309 const BookmarkNode
* parent
,
311 const BookmarkNode
* node
,
312 const std::set
<GURL
>& removed_urls
) {
313 DCHECK(CalledOnValidThread());
314 for (std::set
<GURL
>::const_iterator iter
= removed_urls
.begin();
315 iter
!= removed_urls
.end();
317 RemoveImageForUrl(*iter
);
321 void BookmarkImageService::BookmarkModelLoaded(BookmarkModel
* model
,
322 bool ids_reassigned
) {
325 void BookmarkImageService::BookmarkNodeMoved(BookmarkModel
* model
,
326 const BookmarkNode
* old_parent
,
328 const BookmarkNode
* new_parent
,
332 void BookmarkImageService::BookmarkNodeAdded(BookmarkModel
* model
,
333 const BookmarkNode
* parent
,
337 void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel
* model
,
338 const BookmarkNode
* node
) {
339 DCHECK(CalledOnValidThread());
341 previous_url_
= node
->url();
344 void BookmarkImageService::BookmarkNodeChanged(BookmarkModel
* model
,
345 const BookmarkNode
* node
) {
346 DCHECK(CalledOnValidThread());
347 if (node
->is_url() && previous_url_
!= node
->url())
348 ChangeImageURL(previous_url_
, node
->url());
351 void BookmarkImageService::BookmarkNodeFaviconChanged(
352 BookmarkModel
* model
,
353 const BookmarkNode
* node
) {
356 void BookmarkImageService::BookmarkNodeChildrenReordered(
357 BookmarkModel
* model
,
358 const BookmarkNode
* node
) {
361 void BookmarkImageService::BookmarkAllUserNodesRemoved(
362 BookmarkModel
* model
,
363 const std::set
<GURL
>& removed_urls
) {
367 } // namespace enhanced_bookmarks