Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Side by Side Diff: chrome/browser/android/thumbnail/thumbnail_cache.cc

Issue 262543002: android: add ThumbnailCache (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@codec
Patch Set: separate into multiple files Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
5 #include "chrome/browser/android/thumbnail/thumbnail_cache.h"
6
7 #include <algorithm>
8 #include <cmath>
9
10 #include "base/file_util.h"
11 #include "base/files/file.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_path.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/time/time.h"
16 #include "content/public/browser/android/ui_resource_provider.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "third_party/skia/include/core/SkBitmap.h"
19 #include "third_party/skia/include/core/SkCanvas.h"
20 #include "ui/gfx/geometry/size_conversions.h"
21
22 namespace {
23
24 const bool kDropCachedNTPOnLowMemory = true;
25 const float kApproximationScaleFactor = 4.f;
26 const base::TimeDelta kCaptureMinRequestTimeMs(
27 base::TimeDelta::FromMilliseconds(1000));
28
29 } // anonymous namespace
30
31 ThumbnailCache::ThumbnailCache(const std::string& disk_cache_path_str,
32 size_t default_cache_size,
33 size_t approximation_cache_size,
34 size_t compression_queue_max_size,
35 size_t write_queue_max_size,
36 bool use_approximation_thumbnail)
37 : disk_cache_path_(disk_cache_path_str),
38 compression_queue_max_size_(compression_queue_max_size),
39 write_queue_max_size_(write_queue_max_size),
40 use_approximation_thumbnail_(use_approximation_thumbnail),
41 cache_(default_cache_size),
42 approximation_cache_(approximation_cache_size),
43 ui_resource_provider_(NULL),
44 compression_thread_("thumbnail_compression"),
45 weak_factory_(this) {
46 compression_thread_.Start();
47 }
48
49 ThumbnailCache::~ThumbnailCache() {
50 compression_thread_.Stop();
51 SetUIResourceProvider(NULL);
52 }
53
54 void ThumbnailCache::SetUIResourceProvider(
55 content::UIResourceProvider* ui_resource_provider) {
56 if (ui_resource_provider_ == ui_resource_provider)
57 return;
58
59 approximation_cache_.Clear();
60 cache_.Clear();
61
62 ui_resource_provider_ = ui_resource_provider;
63 }
64
65 void ThumbnailCache::AddThumbnailCacheObserver(
66 ThumbnailCacheObserver* observer) {
67 if (!observers_.HasObserver(observer))
68 observers_.AddObserver(observer);
69 }
70
71 void ThumbnailCache::RemoveThumbnailCacheObserver(
72 ThumbnailCacheObserver* observer) {
73 if (observers_.HasObserver(observer))
74 observers_.RemoveObserver(observer);
75 }
76
77 void ThumbnailCache::Put(TabId tab_id,
78 const SkBitmap& bitmap,
79 float thumbnail_scale) {
80 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
81 return;
82
83 scoped_refptr<ThumbnailImpl> thumbnail = make_scoped_refptr(new ThumbnailImpl(
84 tab_id, bitmap, thumbnail_scale, this, ui_resource_provider_));
85
86 RemoveFromQueues(tab_id);
87 MakeSpaceForNewItemIfNecessary(tab_id);
88 cache_.Put(tab_id, thumbnail);
89
90 if (use_approximation_thumbnail_) {
91 std::pair<SkBitmap, float> approximation =
92 CreateApproximation(bitmap, thumbnail_scale);
93 scoped_refptr<ThumbnailImpl> approx_thumbnail =
94 make_scoped_refptr(new ThumbnailImpl(tab_id,
95 approximation.first,
96 approximation.second,
97 this,
98 ui_resource_provider_));
99 approximation_cache_.Put(tab_id, approx_thumbnail);
100 }
101
102 CompressThumbnailIfNecessary(thumbnail);
103 }
104
105 void ThumbnailCache::Remove(TabId tab_id) {
106 cache_.Remove(tab_id);
107 approximation_cache_.Remove(tab_id);
108 thumbnail_meta_data_.erase(tab_id);
109 RemoveFileFromDisk(GetFilePath(tab_id));
110 RemoveFromQueues(tab_id);
111 }
112
113 scoped_refptr<Thumbnail> ThumbnailCache::Get(TabId tab_id) {
114 scoped_refptr<ThumbnailImpl> thumbnail = cache_.Get(tab_id);
115 if (!thumbnail)
116 thumbnail = approximation_cache_.Get(tab_id);
117
118 // If we are trying to find a thumbnail that is not in memory cache and yet is
119 // visible, then try paging it in from disk.
120 if (!thumbnail &&
121 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
122 visible_ids_.end() &&
123 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
124 read_queue_.end()) {
125 read_queue_.push_back(tab_id);
126 ReadNextThumbnail();
127 }
128 return thumbnail;
129 }
130
131 void ThumbnailCache::RemoveFromDiskAtAndAboveId(TabId min_id) {
132 base::Closure remove_task =
133 base::Bind(&ThumbnailCache::RemoveFromDiskAtAndAboveIdTask,
134 disk_cache_path_,
135 min_id);
136 content::BrowserThread::PostTask(
137 content::BrowserThread::FILE, FROM_HERE, remove_task);
138 }
139
140 void ThumbnailCache::InvalidateThumbnailIfChanged(TabId tab_id,
141 const GURL& url) {
142 ThumbnailMetaDataMap::iterator meta_data_iter =
143 thumbnail_meta_data_.find(tab_id);
144 if (meta_data_iter == thumbnail_meta_data_.end()) {
145 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
146 } else if (meta_data_iter->second.url() != url) {
147 Remove(tab_id);
148 }
149 }
150
151 bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id,
152 const GURL& url) {
153 ThumbnailMetaDataMap::iterator meta_data_iter =
154 thumbnail_meta_data_.find(tab_id);
155 if (meta_data_iter != thumbnail_meta_data_.end() &&
156 meta_data_iter->second.url() == url &&
157 (CurrentTime() - meta_data_iter->second.capture_time()) <
158 kCaptureMinRequestTimeMs) {
159 return false;
160 }
161
162 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(CurrentTime(), url);
163 return true;
164 }
165
166 void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority) {
167 if (priority.empty()) {
168 visible_ids_.clear();
169 return;
170 }
171
172 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
173 if (visible_ids_.size() == ids_size) {
174 // Early out if called with the same input as last time (We only care
175 // about the first mCache.MaximumCacheSize() entries).
176 bool lists_differ = false;
177 for (TabIdList::const_iterator visible_iter = visible_ids_.begin(),
David Trainor- moved to gerrit 2014/06/17 08:12:11 A while loop might look a bit cleaner.
powei 2014/06/19 23:05:59 Done.
178 priority_iter = priority.begin();
179 visible_iter != visible_ids_.end() && priority_iter != priority.end();
180 visible_iter++, priority_iter++) {
181 if (*priority_iter != *visible_iter) {
182 lists_differ = true;
183 break;
184 }
185 }
186 if (!lists_differ)
187 return;
188 }
189
190 read_queue_.clear();
191 visible_ids_.clear();
192 size_t count = 0;
193 for (TabIdList::const_iterator iter = priority.begin();
194 iter != priority.end() && count < ids_size;
195 iter++, count++) {
196 TabId tab_id = *iter;
197 visible_ids_.push_back(tab_id);
198 if (!cache_.Get(tab_id) &&
199 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
David Trainor- moved to gerrit 2014/06/17 08:12:11 Is this to check if the same id is in the list twi
powei 2014/06/19 23:05:59 Yes it is. We probably don't need this if we're s
200 read_queue_.end()) {
201 read_queue_.push_back(tab_id);
202 }
203 }
204
205 ReadNextThumbnail();
206 }
207
208 void ThumbnailCache::RemoveFileFromDisk(const base::FilePath& file_path) {
209 base::Closure task =
210 base::Bind(&ThumbnailCache::RemoveFileFromDiskTask, file_path);
211 content::BrowserThread::PostTask(
212 content::BrowserThread::FILE, FROM_HERE, task);
213 }
214
215 void ThumbnailCache::RemoveFileFromDiskTask(const base::FilePath& file_path) {
216 base::DeleteFile(file_path, false);
217 }
218
219 void ThumbnailCache::RemoveFromDiskAtAndAboveIdTask(
220 const base::FilePath& dir_path,
221 TabId min_id) {
222 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
223 while (true) {
224 base::FilePath path = enumerator.Next();
225 if (path.empty())
226 break;
227 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
228 TabId tab_id;
229 bool success = base::StringToInt(info.GetName().value(), &tab_id);
230 if (success && tab_id >= min_id)
231 base::DeleteFile(path, false);
232 }
233 }
234
235 void ThumbnailCache::WriteThumbnailIfNecessary(
236 scoped_refptr<ThumbnailImpl> thumbnail) {
237 RemoveDuplicateIdsFromQueueHelper(write_queue_, thumbnail);
238 if (write_queue_.size() > write_queue_max_size_)
239 write_queue_.pop_front();
240
241 write_queue_.push_back(thumbnail);
242 WriteNextThumbnail();
243 }
244
245 void ThumbnailCache::CompressThumbnailIfNecessary(
246 scoped_refptr<ThumbnailImpl> thumbnail) {
247 RemoveDuplicateIdsFromQueueHelper(compression_queue_, thumbnail);
248 if (compression_queue_.size() > compression_queue_max_size_)
249 compression_queue_.pop_front();
250
251 compression_queue_.push_back(thumbnail);
252 CompressNextThumbnail();
253 }
254
255 void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
256 if (cache_.Get(tab_id) ||
257 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
258 visible_ids_.end() ||
259 cache_.size() < cache_.MaximumCacheSize()) {
260 return;
261 }
262
263 TabId key_to_remove;
264 bool found_key_to_remove = false;
265
266 // 1. Find a cached item not in this list
267 for (ExpiringThumbnailCache::iterator iter = cache_.begin();
268 iter != cache_.end();
269 iter++) {
270 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
271 visible_ids_.end()) {
272 key_to_remove = iter->first;
273 found_key_to_remove = true;
274 break;
275 }
276 }
277
278 if (!found_key_to_remove) {
279 // 2. Find the least important id we can remove.
280 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
281 riter != visible_ids_.rend();
282 riter++) {
283 if (cache_.Get(*riter)) {
284 key_to_remove = *riter;
285 break;
286 found_key_to_remove = true;
287 }
288 }
289 }
290
291 if (found_key_to_remove)
292 cache_.Remove(key_to_remove);
293 }
294
295 void ThumbnailCache::RemoveFromQueues(TabId tab_id) {
296 TabIdList::iterator read_iter =
297 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
298 if (read_iter != read_queue_.end())
299 read_queue_.erase(read_iter);
300
301 ThumbnailQueue new_write_queue;
302 for (ThumbnailQueue::iterator iter = write_queue_.begin();
303 iter != write_queue_.end();
304 iter++) {
305 scoped_refptr<ThumbnailImpl> write_thumbnail = *iter;
306 if (write_thumbnail->tab_id() != tab_id)
David Trainor- moved to gerrit 2014/06/17 08:12:11 Why not if (==) iter.remove()? Same below and for
powei 2014/06/19 23:05:59 Done. changed to "iter = write_queue_.erase(iter);
307 new_write_queue.push_back(write_thumbnail);
308 }
309 write_queue_ = new_write_queue;
310
311 ThumbnailQueue new_compression_queue;
312 for (ThumbnailQueue::iterator iter = compression_queue_.begin();
313 iter != compression_queue_.end();
314 iter++) {
315 scoped_refptr<ThumbnailImpl> compress_thumbnail = *iter;
316 if (compress_thumbnail->tab_id() != tab_id)
317 new_compression_queue.push_back(compress_thumbnail);
318 }
319 compression_queue_ = new_compression_queue;
320 }
321
322 bool ThumbnailCache::RemoveDuplicateIdsFromQueueHelper(
323 ThumbnailQueue& queue,
324 scoped_refptr<ThumbnailImpl> thumbnail) {
325 bool found = false;
326 ThumbnailQueue::iterator iter = queue.begin();
327 ThumbnailQueue good_queue;
328 for (ThumbnailQueue::iterator iter = queue.begin(); iter != queue.end();
329 iter++) {
330 if ((*iter)->tab_id() != thumbnail->tab_id()) {
331 good_queue.push_back(thumbnail);
332 } else {
333 found = true;
334 }
335 }
336 queue = good_queue;
337 return found;
338 }
339
340 void ThumbnailCache::InvalidateCachedThumbnail(
341 scoped_refptr<ThumbnailImpl> thumbnail) {
342 TabId tab_id = thumbnail->tab_id();
343 if (cache_.Get(tab_id) == thumbnail)
344 cache_.Remove(tab_id);
345
346 if (approximation_cache_.Get(tab_id) == thumbnail)
347 approximation_cache_.Remove(tab_id);
348 }
349
350 base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) const {
351 return disk_cache_path_.Append(base::IntToString(tab_id));
352 }
353
354 base::Time ThumbnailCache::CurrentTime() {
355 return base::Time::Now();
356 }
357
358 void ThumbnailCache::WriteNextThumbnail() {
David Trainor- moved to gerrit 2014/06/17 08:12:11 What if we're already waiting for a write to finis
powei 2014/06/19 23:05:59 Are you thinking about how the size of the write_q
David Trainor- moved to gerrit 2014/06/25 07:40:15 I'm more worried that we have a write queue that d
powei 2014/07/07 17:24:51 Agreed offline that we'd keep a count instead of a
359 if (write_queue_.empty())
360 return;
361
362 scoped_refptr<ThumbnailImpl> thumbnail = write_queue_.front();
363 write_queue_.pop_front();
364
365 if (!thumbnail->IsValid())
366 return;
367
368 scoped_refptr<ThumbnailImpl> in_thumbnail =
369 thumbnail->PassDataToNewThumbnail();
David Trainor- moved to gerrit 2014/06/17 08:12:11 Won't this steal the reference from the object in
powei 2014/06/19 23:05:58 The point is to steal the reference. This way we'
David Trainor- moved to gerrit 2014/06/25 07:40:16 But since we're building the ui resource id on dem
powei 2014/07/07 17:24:51 Done. Moved to more explicit passing of ref-counte
370
371 base::Closure write_task = base::Bind(&ThumbnailCache::WriteTask,
372 in_thumbnail,
373 GetFilePath(thumbnail->tab_id()));
374 base::Closure post_write_task = base::Bind(
375 &ThumbnailCache::WriteNextThumbnail, weak_factory_.GetWeakPtr());
376
377 content::BrowserThread::PostTaskAndReply(
378 content::BrowserThread::FILE, FROM_HERE, write_task, post_write_task);
379 }
380
381 void ThumbnailCache::WriteTask(scoped_refptr<ThumbnailImpl> thumbnail,
382 const base::FilePath& file_path) {
383 DCHECK(thumbnail);
384 DCHECK(thumbnail->IsValid());
385
386 base::File file(file_path,
387 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
388 bool success = ThumbnailImpl::WriteToFile(thumbnail, file);
389 file.Close();
390
391 if (!success)
392 RemoveFileFromDisk(file_path);
David Trainor- moved to gerrit 2014/06/17 08:12:11 Is posting this safe? What if the app dies before
powei 2014/06/19 23:05:59 Done. Good catch. called DeleteFile directly ins
393 }
394
395 void ThumbnailCache::ReadNextThumbnail() {
396 if (read_queue_.empty())
397 return;
398
399 TabId tab_id = read_queue_.front();
400
401 // Create an empty thumbnail to hold the content of the read.
402 scoped_refptr<ThumbnailImpl> output_thumbnail = make_scoped_refptr(
403 new ThumbnailImpl(tab_id, SkBitmap(), 0.f, this, ui_resource_provider_));
404
405 base::FilePath file_path = GetFilePath(tab_id);
406 base::Closure read_task =
407 base::Bind(&ThumbnailCache::ReadTask, file_path, output_thumbnail);
408 base::Closure post_read_task = base::Bind(&ThumbnailCache::PostReadTask,
409 weak_factory_.GetWeakPtr(),
410 output_thumbnail);
411 content::BrowserThread::PostTaskAndReply(
412 content::BrowserThread::FILE, FROM_HERE, read_task, post_read_task);
413 }
414
415 void ThumbnailCache::ReadTask(const base::FilePath& file_path,
416 scoped_refptr<ThumbnailImpl> thumbnail) {
417 DCHECK(thumbnail);
418 if (!base::PathExists(file_path))
419 return;
420
421 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
422 DCHECK(file.IsValid());
423 bool success = ThumbnailImpl::ReadFromFile(file, thumbnail);
424 file.Close();
425
426 if (!success)
427 RemoveFileFromDisk(file_path);
428 }
429
430 void ThumbnailCache::PostReadTask(scoped_refptr<ThumbnailImpl> thumbnail) {
431 DCHECK(thumbnail);
432 TabIdList::iterator iter =
433 std::find(read_queue_.begin(), read_queue_.end(), thumbnail->tab_id());
434 if (iter == read_queue_.end())
435 return;
436
437 read_queue_.erase(iter);
438 TabId tab_id = thumbnail->tab_id();
439 MakeSpaceForNewItemIfNecessary(tab_id);
440 cache_.Put(tab_id, thumbnail);
441 NotifyObserversOfThumbnailRead(tab_id);
442 ReadNextThumbnail();
443 }
444
445 void ThumbnailCache::CompressNextThumbnail() {
David Trainor- moved to gerrit 2014/06/17 08:12:11 Shouldn't this queue up the tasks and only run one
powei 2014/06/19 23:05:59 see comment for write task.
446 if (!compression_queue_.empty()) {
447 scoped_refptr<ThumbnailImpl> thumbnail = compression_queue_.front();
448 compression_queue_.pop_front();
449
450 scoped_refptr<ThumbnailImpl> in_thumbnail =
451 thumbnail->PassDataToNewThumbnail();
David Trainor- moved to gerrit 2014/06/17 08:12:11 Doesn't this also steal the bitmap/data from the t
powei 2014/06/19 23:05:59 This does steal the reference, and once compressio
David Trainor- moved to gerrit 2014/06/25 07:40:15 Still not sure how this works if we compress befor
452
453 scoped_refptr<ThumbnailImpl> out_thumbnail =
454 scoped_refptr<ThumbnailImpl>(new ThumbnailImpl(in_thumbnail->tab_id(),
455 SkBitmap(),
456 0.f,
457 this,
458 ui_resource_provider_));
459 base::Closure compression_task =
460 base::Bind(&ThumbnailImpl::Compress, in_thumbnail, out_thumbnail);
461 base::Closure post_compression_task =
462 base::Bind(&ThumbnailCache::PostCompressionTask,
463 weak_factory_.GetWeakPtr(),
464 out_thumbnail);
465
466 DCHECK(compression_thread_.message_loop_proxy());
467 compression_thread_.message_loop_proxy()->PostTaskAndReply(
468 FROM_HERE, compression_task, post_compression_task);
469 }
470 }
471
472 void ThumbnailCache::PostCompressionTask(
473 scoped_refptr<ThumbnailImpl> compressed_thumbnail) {
474 DCHECK(compressed_thumbnail);
475 TabId tab_id = compressed_thumbnail->tab_id();
476 cache_.Remove(tab_id);
477 if (compressed_thumbnail->IsValid()) {
478 cache_.Put(tab_id, compressed_thumbnail);
479 WriteThumbnailIfNecessary(compressed_thumbnail);
480 }
481 CompressNextThumbnail();
482 }
483
484 void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id) {
485 FOR_EACH_OBSERVER(
486 ThumbnailCacheObserver, observers_, OnFinishedThumbnailRead(tab_id));
487 }
488
489 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() {
490 }
491
492 ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
493 const base::Time& current_time,
494 const GURL& url)
495 : capture_time_(current_time), url_(url) {
496 }
497
498 std::pair<SkBitmap, float> ThumbnailCache::CreateApproximation(
499 const SkBitmap& bitmap,
500 float scale) {
501 DCHECK(!bitmap.empty());
502 DCHECK_GT(scale, 0);
503 SkAutoLockPixels bitmap_lock(bitmap);
504 float new_scale = 1.f / kApproximationScaleFactor;
505
506 gfx::Size dst_size = gfx::ToFlooredSize(
507 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale));
508 SkBitmap dst_bitmap;
509 dst_bitmap.setConfig(bitmap.getConfig(), dst_size.width(), dst_size.height());
510 dst_bitmap.allocPixels();
511 dst_bitmap.eraseColor(0);
512 SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
513
514 SkCanvas canvas(dst_bitmap);
515 canvas.scale(new_scale, new_scale);
516 canvas.drawBitmap(bitmap, 0, 0, NULL);
517 dst_bitmap.setImmutable();
518
519 return std::make_pair(dst_bitmap, new_scale * scale);
520 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698