summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-04 21:43:53 +0000
committerbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-04 21:43:53 +0000
commit1984a5fb88d86ccd8289917caa3f64f4a4afc297 (patch)
tree4c86f01bfa844798b0a2579f9036b2169c3117e5 /chrome/browser
parenta2068a61d67a2e90789e45a8073e631899bdeb9c (diff)
downloadchromium_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.cc206
-rw-r--r--chrome/browser/thumbnail_store.h57
-rw-r--r--chrome/browser/thumbnail_store_unittest.cc174
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();
+}