diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-04 21:43:53 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-04 21:43:53 +0000 |
commit | 1984a5fb88d86ccd8289917caa3f64f4a4afc297 (patch) | |
tree | 4c86f01bfa844798b0a2579f9036b2169c3117e5 /chrome/browser | |
parent | a2068a61d67a2e90789e45a8073e631899bdeb9c (diff) | |
download | chromium_src-1984a5fb88d86ccd8289917caa3f64f4a4afc297.zip chromium_src-1984a5fb88d86ccd8289917caa3f64f4a4afc297.tar.gz chromium_src-1984a5fb88d86ccd8289917caa3f64f4a4afc297.tar.bz2 |
Implement a first pass at thumbnail store storage to disk, including a unit test for the new behavior.
TEST=covered by unittest
BUG=none
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/thumbnail_store.cc | 206 | ||||
-rw-r--r-- | chrome/browser/thumbnail_store.h | 57 | ||||
-rw-r--r-- | chrome/browser/thumbnail_store_unittest.cc | 174 |
3 files changed, 427 insertions, 10 deletions
diff --git a/chrome/browser/thumbnail_store.cc b/chrome/browser/thumbnail_store.cc new file mode 100644 index 0000000..1745431 --- /dev/null +++ b/chrome/browser/thumbnail_store.cc @@ -0,0 +1,206 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/thumbnail_store.h" + +#include <string.h> + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "base/file_util.h" +#include "base/gfx/jpeg_codec.h" +#include "base/thread.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/thumbnail_score.h" +#include "googleurl/src/gurl.h" +#include "third_party/skia/include/core/SkBitmap.h" + +ThumbnailStore::ThumbnailStore() : cache_(NULL), cache_initialized_(false) { +} + +ThumbnailStore::~ThumbnailStore() { +} + +void ThumbnailStore::Init(const FilePath& file_path) { + file_path_ = file_path.DirName(); + g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &ThumbnailStore::GetAllThumbnailsFromDisk, + file_path_, MessageLoop::current())); +} + +void ThumbnailStore::OnDiskDataAvailable(ThumbnailStore::Cache* cache) { + if (cache) { + cache_.reset(cache); + cache_initialized_ = true; + } +} + +void ThumbnailStore::GetAllThumbnailsFromDisk(FilePath filepath, + MessageLoop* cb_loop) { + // Create the specified directory if it does not exist. + if (!file_util::DirectoryExists(filepath) && + !file_util::CreateDirectory(filepath)) + return; + + // Walk the directory and read the thumbnail data from disk. + FilePath path; + GURL url; + SkBitmap image; + ThumbnailScore score; + ThumbnailStore::Cache* cache = new ThumbnailStore::Cache; + file_util::FileEnumerator fenum(filepath, false, + file_util::FileEnumerator::FILES); + + while (!(path = fenum.Next()).empty()) { + if (GetPageThumbnailFromDisk(path, &url, &image, &score)) + (*cache)[url] = std::make_pair(image, score); + } + + cb_loop->PostTask(FROM_HERE, + NewRunnableMethod(this, &ThumbnailStore::OnDiskDataAvailable, cache)); +} + +bool ThumbnailStore::GetPageThumbnailFromDisk(const FilePath& file, + GURL* url, SkBitmap* thumbnail, + ThumbnailScore* score) const { + int64 file_size; + if (!file_util::GetFileSize(file, &file_size)) + return false; + + // Read the file into a buffer. + std::vector<char> data; + data.resize(static_cast<unsigned int>(file_size)); + if (file_util::ReadFile(file, &data[0], static_cast<int>(file_size)) == -1) + return false; + + // Unpack the url, ThumbnailScore and JPEG size from the buffer. + std::string url_string; + unsigned int jpeg_len; + void* iter = NULL; + Pickle packed(&data[0], static_cast<int>(file_size)); + + if (!packed.ReadString(&iter, &url_string) || + !UnpackScore(score, packed, iter) || + !packed.ReadUInt32(&iter, &jpeg_len)) + return false; + + // Store the url to the out parameter. + GURL temp_url(url_string); + url->Swap(&temp_url); + + // Unpack the JPEG data from the buffer. + if (thumbnail) { + const char* jpeg_data = NULL; + int out_len; + + if (!packed.ReadData(&iter, &jpeg_data, &out_len) || + out_len != jpeg_len) + return false; + + // Convert the jpeg_data to an SkBitmap. + SkBitmap* thumbnail_ = JPEGCodec::Decode( + reinterpret_cast<const unsigned char*>(jpeg_data), jpeg_len); + *thumbnail = *thumbnail_; + delete thumbnail_; + } + return true; +} + +bool ThumbnailStore::SetPageThumbnail(const GURL& url, + SkBitmap& thumbnail, + const ThumbnailScore& score, + bool write_to_disk) { + if (!cache_initialized_) + return false; + + // If a thumbnail already exists, check if it should be replaced. + if (cache_->find(url) != cache_->end() && + !ShouldReplaceThumbnailWith((*cache_)[url].second, score)) + return true; + + // Update the cache_ with the new thumbnail. + (*cache_)[url] = std::make_pair(thumbnail, score); + + // Write the new thumbnail data to disk in the background on file_thread. + if (write_to_disk) { + g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &ThumbnailStore::WriteThumbnailToDisk, url)); + } + return true; +} + +bool ThumbnailStore::WriteThumbnailToDisk(const GURL& url) const { + // Thumbnail data will be stored in a file named url.host(). + FilePath file = file_path_.AppendASCII(url.host()); + Pickle packed; + SkBitmap thumbnail = (*cache_)[url].first; + ThumbnailScore score = (*cache_)[url].second; + + // Convert the SkBitmap to a JPEG. + std::vector<unsigned char> jpeg_data; + SkAutoLockPixels thumbnail_lock(thumbnail); + bool encoded = JPEGCodec::Encode( + reinterpret_cast<unsigned char*>(thumbnail.getAddr32(0, 0)), + JPEGCodec::FORMAT_BGRA, thumbnail.width(), + thumbnail.height(), + static_cast<int>(thumbnail.rowBytes()), 90, + &jpeg_data); + + if (!encoded) + return false; + + // Pack the url, ThumbnailScore, and the JPEG data. + packed.WriteString(url.spec()); + PackScore(score, &packed); + packed.WriteUInt32(jpeg_data.size()); + packed.WriteData(reinterpret_cast<char*>(&jpeg_data[0]), jpeg_data.size()); + + // Write the packed data to a file. + file_util::Delete(file, false); + return file_util::WriteFile(file, + reinterpret_cast<const char*>(packed.data()), + packed.size()) != -1; +} + +bool ThumbnailStore::GetPageThumbnail(const GURL& url, SkBitmap* thumbnail, + ThumbnailScore* score) { + if (!cache_initialized_ || + cache_->find(url) == cache_->end()) + return false; + + *thumbnail = (*cache_)[url].first; + *score = (*cache_)[url].second; + return true; +} + +void ThumbnailStore::PackScore(const ThumbnailScore& score, + Pickle* packed) const { + // Pack the contents of the given ThumbnailScore into the given Pickle. + packed->WriteData(reinterpret_cast<const char*>(&score.boring_score), + sizeof(score.boring_score)); + packed->WriteBool(score.at_top); + packed->WriteBool(score.good_clipping); + packed->WriteInt64(score.time_at_snapshot.ToInternalValue()); +} + +bool ThumbnailStore::UnpackScore(ThumbnailScore* score, const Pickle& packed, + void*& iter) const { + // Unpack a ThumbnailScore from the given Pickle and iterator. + const char* boring = NULL; + int out_len; + int64 us; + + if (!packed.ReadData(&iter, &boring, &out_len) || + !packed.ReadBool(&iter, &score->at_top) || + !packed.ReadBool(&iter, &score->good_clipping) || + !packed.ReadInt64(&iter, &us)) + return false; + + if (out_len != sizeof(score->boring_score)) + return false; + + memcpy(&score->boring_score, boring, sizeof(score->boring_score)); + score->time_at_snapshot = base::Time::FromInternalValue(us); + return true; +} diff --git a/chrome/browser/thumbnail_store.h b/chrome/browser/thumbnail_store.h index bfff59a..46b6ae0 100644 --- a/chrome/browser/thumbnail_store.h +++ b/chrome/browser/thumbnail_store.h @@ -5,11 +5,16 @@ #ifndef CHROME_BROWSER_THUMBNAIL_STORE_H_ #define CHROME_BROWSER_THUMBNAIL_STORE_H_ +#include <map> #include <vector> #include "base/file_path.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "testing/gtest/include/gtest/gtest_prod.h" class GURL; +class Pickle; class SkBitmap; struct ThumbnailScore; namespace base { @@ -18,7 +23,7 @@ class Time; // This storage interface provides storage for the thumbnails used // by the new_tab_ui. -class ThumbnailStore { +class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { public: ThumbnailStore(); ~ThumbnailStore(); @@ -26,34 +31,66 @@ class ThumbnailStore { // Must be called after creation but before other methods are called. // file_path is a directory where a new database should be created // or the location of an existing databse. - // If false is returned, no other methods should be called. - bool Init(const FilePath& file_path); + void Init(const FilePath& file_path); - // Stores the given thumbnail and score with the associated url. + // Stores the given thumbnail and score with the associated url in the cache. + // If write_to_disk is true, the thumbnail data is written to disk on the + // file_thread. bool SetPageThumbnail(const GURL& url, - const SkBitmap& thumbnail, + SkBitmap& thumbnail, const ThumbnailScore& score, - const base::Time& time); + bool write_to_disk); // Retrieves the thumbnail and score for the given url. // Returns false if there is not data for the given url or some other // error occurred. bool GetPageThumbnail(const GURL& url, - SkBitmap** thumbnail, - ThumbnailScore& score); + SkBitmap* thumbnail, + ThumbnailScore* score); private: + FRIEND_TEST(ThumbnailStoreTest, RetrieveFromCache); + FRIEND_TEST(ThumbnailStoreTest, RetrieveFromDisk); + + // Data structure used to store thumbnail data in memory. + typedef std::map<GURL, std::pair<SkBitmap, ThumbnailScore> > Cache; + // The location of the thumbnail store. FilePath file_path_; + // Read all thumbnail data from the specified FilePath into a Cache object. + // Done on the file_thread and returns to OnDiskDataAvailable on the thread + // owning the specified MessageLoop. + void GetAllThumbnailsFromDisk(FilePath filepath, MessageLoop* cb_loop); + + // Read the thumbnail data from the given file and stores it in the + // out parameters GURL, SkBitmap, and ThumbnailScore. + bool GetPageThumbnailFromDisk(const FilePath& file, + GURL* url, + SkBitmap* thumbnail, + ThumbnailScore* score) const; + + // Once thumbnail data from the disk is available from the file_thread, + // this function is invoked on the main thread. It takes ownership of the + // Cache* passed in and retains this Cache* for the lifetime of the object. + void OnDiskDataAvailable(ThumbnailStore::Cache* cache); + + // Write thumbnail data to disk for a given url. + bool WriteThumbnailToDisk(const GURL& url) const; + // Pack the given ThumbnailScore into the given Pickle. - void PackScore(const ThumbnailScore& score, Pickle& packed); + void PackScore(const ThumbnailScore& score, Pickle* packed) const; // Unpack a ThumbnailScore from a given Pickle and associated iterator. // Returns false is a ThumbnailScore could not be unpacked. - bool UnpackScore(ThumbnailScore* score, const Pickle& packed, void*& iter); + bool UnpackScore(ThumbnailScore* score, const Pickle& packed, + void*& iter) const; DISALLOW_COPY_AND_ASSIGN(ThumbnailStore); + + // The Cache maintained by the object. + scoped_ptr<ThumbnailStore::Cache> cache_; + bool cache_initialized_; }; #endif // CHROME_BROWSER_THUMBNAIL_STORE_H_ diff --git a/chrome/browser/thumbnail_store_unittest.cc b/chrome/browser/thumbnail_store_unittest.cc new file mode 100644 index 0000000..53ec322 --- /dev/null +++ b/chrome/browser/thumbnail_store_unittest.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string.h> +#include <iostream> + +#include "chrome/browser/thumbnail_store.h" + +#include "base/time.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/gfx/jpeg_codec.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/thumbnail_score.h" +#include "chrome/tools/profiles/thumbnail-inl.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkPixelRef.h" + +inline unsigned int diff(unsigned int a, unsigned int b) { + return a>b ? a-b : b-a; +} + +class ThumbnailStoreTest : public testing::Test { + public: + ThumbnailStoreTest() : score1_(.5, true, false), + url1_("http://www.google.com"), url2_("http://www.elgoog.com") { + } + ~ThumbnailStoreTest() { + } + + protected: + void SetUp(); + + void TearDown() { + file_util::Delete(file_path_.AppendASCII(url1_.host()), false); + file_util::Delete(file_path_.AppendASCII(url2_.host()), false); + } + + // Compute the max difference over all pixels for each RGBA component. + void PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b); + + // The directory where ThumbnailStore will store data. + FilePath file_path_; + + SkBitmap read_image_, image_enc_dec_, image_; + ThumbnailScore score1_, score2_; + GURL url1_, url2_; + base::Time time_; +}; + +void ThumbnailStoreTest::SetUp() { + if (!file_util::GetTempDir(&file_path_)) + FAIL(); + + // Delete any old thumbnail files if they exist. + file_util::Delete(file_path_.AppendASCII(url1_.host()), false); + file_util::Delete(file_path_.AppendASCII(url2_.host()), false); + + // image is the original SkBitmap representing the thumbnail + image_ = *(JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); + + // The ThumbnailStore will encode the thumbnail to jpeg at 90% quality, so + // we do this to image. When image is stored, image_enc_dec_ is what + // ThumbnailStore should return. + std::vector<unsigned char> jpeg_data; + SkAutoLockPixels thumbnail_lock(image_); + bool encoded = JPEGCodec::Encode( + reinterpret_cast<unsigned char*>(image_.getAddr32(0, 0)), + JPEGCodec::FORMAT_BGRA, image_.width(), + image_.height(), + static_cast<int>(image_.rowBytes()), 90, + &jpeg_data); + + image_enc_dec_ = *(JPEGCodec::Decode( + reinterpret_cast<const unsigned char*>(&jpeg_data[0]), + jpeg_data.size())); +} + +void ThumbnailStoreTest::PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b) { + // Compute the maximum difference in each of the RGBA components across all + // pixels between the retrieved SkBitmap and the original. These + // differences should be small since encoding was done at 90% before + // writing to disk. + + SkAutoLockPixels lock_a(*image_a); + SkAutoLockPixels lock_b(*image_b); + + int ppr = read_image_.rowBytesAsPixels(); + unsigned int *a, *b; + unsigned int maxv[4]; + memset(maxv, 0, sizeof(maxv)); + + for (int nrows = read_image_.height()-1; nrows >= 0; nrows--) { + a = image_a->getAddr32(0, nrows); + b = image_b->getAddr32(0, nrows); + for (int i = 0; i < ppr; i += 4) { + for (int j = 0; j < 4; j++) { + maxv[j] = std::max(diff(*(a+i) >> (j<<3) & 0xff, + *(b+i) >> (j<<3) & 0xff), + maxv[j]); + } + } + } + + std::cout << "Max diff btwn original and encoded image (b,g,r,a) = (" + << maxv[0] << "," + << maxv[1] << "," + << maxv[2] << "," + << maxv[3] << ")" << std::endl; +} + +TEST_F(ThumbnailStoreTest, RetrieveFromCache) { + ThumbnailStore* store = new ThumbnailStore; + store->AddRef(); + store->file_path_ = file_path_; + store->cache_.reset(new ThumbnailStore::Cache); + store->cache_initialized_ = true; + + read_image_.reset(); + + // Retrieve a thumbnail/score for a nonexistent page. + + EXPECT_FALSE(store->GetPageThumbnail(url2_, &read_image_, &score2_)); + + // Store a thumbnail into the cache and retrieve it. + + EXPECT_TRUE(store->SetPageThumbnail(url1_, image_, score1_, false)); + EXPECT_TRUE(store->GetPageThumbnail(url1_, &read_image_, &score2_)); + EXPECT_TRUE(score1_.Equals(score2_)); + EXPECT_TRUE(read_image_.getSize() == image_.getSize()); + EXPECT_TRUE(read_image_.pixelRef() == image_.pixelRef()); + EXPECT_FALSE(read_image_.isNull()); + + store->Release(); +} + +TEST_F(ThumbnailStoreTest, RetrieveFromDisk) { + ThumbnailStore* store = new ThumbnailStore; + store->AddRef(); + store->file_path_ = file_path_; + store->cache_.reset(new ThumbnailStore::Cache); + store->cache_initialized_ = true; + + read_image_.reset(); + + // Store a thumbnail onto the disk and retrieve it. + + EXPECT_TRUE(store->SetPageThumbnail(url1_, image_, score1_, false)); + EXPECT_TRUE(store->WriteThumbnailToDisk(url1_)); + EXPECT_TRUE(store->GetPageThumbnailFromDisk( + file_path_.AppendASCII(url1_.host()), &url2_, &read_image_, &score2_)); + EXPECT_TRUE(url1_ == url2_); + EXPECT_TRUE(score1_.Equals(score2_)); + EXPECT_TRUE(read_image_.getSize() == image_enc_dec_.getSize()); + EXPECT_FALSE(read_image_.isNull()); + + // The retrieved SkBitmap should be the same as the original image + // encoded at 90% quality to jpeg, then decoded. + + { + SkAutoLockPixels lock_a(read_image_); + SkAutoLockPixels lock_b(image_enc_dec_); + EXPECT_TRUE(0 == memcmp(read_image_.getPixels(), + image_enc_dec_.getPixels(), + read_image_.getSize())); + } + PrintPixelDiff(&read_image_, &image_); + + store->Release(); +} |