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"
5
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"
14
15namespace {
16
17const char kSequenceToken[] = "BookmarkImagesSequenceToken";
18
19void ConstructPersistentImageStore(PersistentImageStore* store,
20                                   const base::FilePath& path) {
21  DCHECK(store);
22  new (store) PersistentImageStore(path);
23}
24
25void DeleteImageStore(ImageStore* store) {
26  DCHECK(store);
27  delete store;
28}
29
30void RetrieveImageFromStoreRelay(
31    ImageStore* store,
32    const GURL& page_url,
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));
38}
39
40}  // namespace
41
42namespace enhanced_bookmarks {
43BookmarkImageService::BookmarkImageService(
44    scoped_ptr<ImageStore> store,
45    EnhancedBookmarkModel* enhanced_bookmark_model,
46    scoped_refptr<base::SequencedWorkerPool> pool)
47    : enhanced_bookmark_model_(enhanced_bookmark_model),
48      store_(store.Pass()),
49      pool_(pool) {
50  DCHECK(CalledOnValidThread());
51  enhanced_bookmark_model_->bookmark_model()->AddObserver(this);
52}
53
54BookmarkImageService::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.
68  //
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));
77  store_.reset(store);
78  pool_->PostNamedSequencedWorkerTask(
79      kSequenceToken,
80      FROM_HERE,
81      base::Bind(&ConstructPersistentImageStore, store, path));
82}
83
84BookmarkImageService::~BookmarkImageService() {
85  DCHECK(CalledOnValidThread());
86  pool_->PostNamedSequencedWorkerTask(
87      kSequenceToken,
88      FROM_HERE,
89      base::Bind(&DeleteImageStore, store_.release()));
90}
91
92void BookmarkImageService::Shutdown() {
93  DCHECK(CalledOnValidThread());
94  enhanced_bookmark_model_->bookmark_model()->RemoveObserver(this);
95  enhanced_bookmark_model_ = NULL;
96}
97
98void BookmarkImageService::SalientImageForUrl(const GURL& page_url,
99                                              Callback callback) {
100  DCHECK(CalledOnValidThread());
101  SalientImageForUrl(page_url, true, callback);
102}
103
104void BookmarkImageService::RetrieveImageFromStore(
105    const GURL& page_url,
106    BookmarkImageService::Callback callback) {
107  DCHECK(CalledOnValidThread());
108  pool_->PostSequencedWorkerTaskWithShutdownBehavior(
109      pool_->GetNamedSequenceToken(kSequenceToken),
110      FROM_HERE,
111      base::Bind(&RetrieveImageFromStoreRelay,
112                 base::Unretained(store_.get()),
113                 page_url,
114                 callback,
115                 base::ThreadTaskRunnerHandle::Get()),
116      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
117}
118
119void 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.
124
125  in_progress_page_urls_.insert(page_url);
126
127  const BookmarkNode* bookmark =
128      enhanced_bookmark_model_->bookmark_model()
129          ->GetMostRecentlyAddedUserNodeForURL(page_url);
130  GURL image_url;
131  if (bookmark) {
132    int width;
133    int height;
134    enhanced_bookmark_model_->GetThumbnailImage(
135        bookmark, &image_url, &width, &height);
136  }
137
138  RetrieveSalientImage(
139      page_url,
140      image_url,
141      "",
142      net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
143      false);
144}
145
146void 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);
156  } else {
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);
163  }
164}
165
166void BookmarkImageService::SalientImageForUrl(const GURL& page_url,
167                                              bool fetch_from_bookmark,
168                                              Callback callback) {
169  DCHECK(CalledOnValidThread());
170
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);
175    return;
176  }
177
178  if (!fetch_from_bookmark) {
179    RetrieveImageFromStore(page_url, callback);
180  } else {
181    RetrieveImageFromStore(page_url,
182                           base::Bind(&BookmarkImageService::FetchCallback,
183                                      base::Unretained(this),
184                                      page_url,
185                                      callback));
186  }
187}
188
189void 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);
201    if (bookmark) {
202      const gfx::Size& size = image.Size();
203      bool result = enhanced_bookmark_model_->SetOriginalImage(
204          bookmark, image_url, size.width(), size.height());
205      DCHECK(result);
206    }
207  }
208}
209
210bool BookmarkImageService::IsPageUrlInProgress(const GURL& page_url) {
211  DCHECK(CalledOnValidThread());
212  return in_progress_page_urls_.find(page_url) != in_progress_page_urls_.end();
213}
214
215void 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(
221        kSequenceToken,
222        FROM_HERE,
223        base::Bind(&ImageStore::Insert,
224                   base::Unretained(store_.get()),
225                   page_url,
226                   image_url,
227                   image));
228  }
229}
230
231void BookmarkImageService::RemoveImageForUrl(const GURL& page_url) {
232  DCHECK(CalledOnValidThread());
233  pool_->PostNamedSequencedWorkerTask(
234      kSequenceToken,
235      FROM_HERE,
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());
239}
240
241void BookmarkImageService::ChangeImageURL(const GURL& from, const GURL& to) {
242  DCHECK(CalledOnValidThread());
243  pool_->PostNamedSequencedWorkerTask(kSequenceToken,
244                                      FROM_HERE,
245                                      base::Bind(&ImageStore::ChangeImageURL,
246                                                 base::Unretained(store_.get()),
247                                                 from,
248                                                 to));
249  in_progress_page_urls_.erase(from);
250  ProcessRequests(from, gfx::Image(), GURL());
251}
252
253void BookmarkImageService::ClearAll() {
254  DCHECK(CalledOnValidThread());
255  // Clears and executes callbacks.
256  pool_->PostNamedSequencedWorkerTask(
257      kSequenceToken,
258      FROM_HERE,
259      base::Bind(&ImageStore::ClearAll, base::Unretained(store_.get())));
260
261  for (std::map<const GURL, std::vector<Callback> >::const_iterator it =
262           callbacks_.begin();
263       it != callbacks_.end();
264       ++it) {
265    ProcessRequests(it->first, gfx::Image(), GURL());
266  }
267
268  in_progress_page_urls_.erase(in_progress_page_urls_.begin(),
269                               in_progress_page_urls_.end());
270}
271
272void BookmarkImageService::ProcessRequests(const GURL& page_url,
273                                           const gfx::Image& image,
274                                           const GURL& image_url) {
275  DCHECK(CalledOnValidThread());
276
277  std::vector<Callback> callbacks = callbacks_[page_url];
278  for (std::vector<Callback>::const_iterator it = callbacks.begin();
279       it != callbacks.end();
280       ++it) {
281    it->Run(image, image_url);
282  }
283
284  callbacks_.erase(page_url);
285}
286
287// BookmarkModelObserver methods.
288
289void BookmarkImageService::BookmarkNodeRemoved(
290    BookmarkModel* model,
291    const BookmarkNode* parent,
292    int old_index,
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();
298       ++iter) {
299    RemoveImageForUrl(*iter);
300  }
301}
302
303void BookmarkImageService::BookmarkModelLoaded(BookmarkModel* model,
304                                               bool ids_reassigned) {
305}
306
307void BookmarkImageService::BookmarkNodeMoved(BookmarkModel* model,
308                                             const BookmarkNode* old_parent,
309                                             int old_index,
310                                             const BookmarkNode* new_parent,
311                                             int new_index) {
312}
313
314void BookmarkImageService::BookmarkNodeAdded(BookmarkModel* model,
315                                             const BookmarkNode* parent,
316                                             int index) {
317}
318
319void BookmarkImageService::OnWillChangeBookmarkNode(BookmarkModel* model,
320                                                    const BookmarkNode* node) {
321  DCHECK(CalledOnValidThread());
322  if (node->is_url())
323    previous_url_ = node->url();
324}
325
326void 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());
331}
332
333void BookmarkImageService::BookmarkNodeFaviconChanged(
334    BookmarkModel* model,
335    const BookmarkNode* node) {
336}
337
338void BookmarkImageService::BookmarkNodeChildrenReordered(
339    BookmarkModel* model,
340    const BookmarkNode* node) {
341}
342
343void BookmarkImageService::BookmarkAllUserNodesRemoved(
344    BookmarkModel* model,
345    const std::set<GURL>& removed_urls) {
346  ClearAll();
347}
348
349}  // namespace enhanced_bookmarks
350