Change ThumbnailStore to use sqlite instead of individual files.

Review URL: http://codereview.chromium.org/149223

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20097 0039d316-1c4b-4281-b951-d872f2087c98
parent 5c943180
...@@ -1016,7 +1016,7 @@ TabRestoreService* ProfileImpl::GetTabRestoreService() { ...@@ -1016,7 +1016,7 @@ TabRestoreService* ProfileImpl::GetTabRestoreService() {
ThumbnailStore* ProfileImpl::GetThumbnailStore() { ThumbnailStore* ProfileImpl::GetThumbnailStore() {
if (!thumbnail_store_.get()) { if (!thumbnail_store_.get()) {
thumbnail_store_ = new ThumbnailStore; thumbnail_store_ = new ThumbnailStore;
thumbnail_store_->Init(GetPath().AppendASCII("thumbnailstore"), this); thumbnail_store_->Init(GetPath().AppendASCII("Top Thumbnails"), this);
} }
return thumbnail_store_.get(); return thumbnail_store_.get();
} }
......
...@@ -1650,8 +1650,8 @@ void TabContents::UpdateThumbnail(const GURL& url, ...@@ -1650,8 +1650,8 @@ void TabContents::UpdateThumbnail(const GURL& url,
const ThumbnailScore& score) { const ThumbnailScore& score) {
// Tell History about this thumbnail // Tell History about this thumbnail
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kThumbnailStore)) { if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kThumbnailStore)) {
profile()->GetThumbnailStore()-> if (!profile()->IsOffTheRecord())
SetPageThumbnail(url, bitmap, score, !profile()->IsOffTheRecord()); profile()->GetThumbnailStore()->SetPageThumbnail(url, bitmap, score);
} else { } else {
HistoryService* hs; HistoryService* hs;
if (!profile()->IsOffTheRecord() && if (!profile()->IsOffTheRecord() &&
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#include <algorithm> #include <algorithm>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/pickle.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/gfx/jpeg_codec.h" #include "base/gfx/jpeg_codec.h"
#include "base/md5.h" #include "base/md5.h"
...@@ -18,51 +17,57 @@ ...@@ -18,51 +17,57 @@
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/profile.h" #include "chrome/browser/profile.h"
#include "chrome/common/pref_service.h" #include "chrome/common/pref_service.h"
#include "chrome/common/thumbnail_score.h" #include "chrome/common/sqlite_utils.h"
#include "googleurl/src/gurl.h" #include "googleurl/src/gurl.h"
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmap.h"
ThumbnailStore::ThumbnailStore() ThumbnailStore::ThumbnailStore()
: cache_(NULL), : cache_(NULL),
cache_initialized_(false), db_(NULL),
hs_(NULL), hs_(NULL),
url_blacklist_(NULL) { url_blacklist_(NULL) {
} }
ThumbnailStore::~ThumbnailStore() { ThumbnailStore::~ThumbnailStore() {
CommitCacheToDB(NULL);
} }
void ThumbnailStore::Init(const FilePath& file_path, Profile* profile) { void ThumbnailStore::Init(const FilePath& db_name,
file_path_ = file_path; Profile* profile) {
// Load thumbnails already in the database.
g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this, &ThumbnailStore::InitializeFromDB,
db_name, MessageLoop::current()));
// Take ownership of a reference to the HistoryService.
hs_ = profile->GetHistoryService(Profile::EXPLICIT_ACCESS); hs_ = profile->GetHistoryService(Profile::EXPLICIT_ACCESS);
// Store a pointer to a persistent table of blacklisted URLs.
url_blacklist_ = profile->GetPrefs()-> url_blacklist_ = profile->GetPrefs()->
GetMutableDictionary(prefs::kNTPMostVisitedURLsBlacklist); GetMutableDictionary(prefs::kNTPMostVisitedURLsBlacklist);
g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, // Get the list of most visited URLs and redirect information from the
NewRunnableMethod(this, &ThumbnailStore::GetAllThumbnailsFromDisk, // HistoryService.
file_path_, MessageLoop::current()));
timer_.Start(base::TimeDelta::FromMinutes(30), this, timer_.Start(base::TimeDelta::FromMinutes(30), this,
&ThumbnailStore::UpdateURLData); &ThumbnailStore::UpdateURLData);
UpdateURLData(); UpdateURLData();
} }
bool ThumbnailStore::SetPageThumbnail(const GURL& url, bool ThumbnailStore::SetPageThumbnail(const GURL& url,
const SkBitmap& thumbnail, const SkBitmap& thumbnail,
const ThumbnailScore& score, const ThumbnailScore& score) {
bool write_to_disk) { if (!cache_.get())
if (!cache_initialized_)
return false; return false;
if (!ShouldStoreThumbnailForURL(url) || if (!ShouldStoreThumbnailForURL(url) ||
(cache_->find(url) != cache_->end() && (cache_->find(url) != cache_->end() &&
!ShouldReplaceThumbnailWith((*cache_)[url].second, score))) !ShouldReplaceThumbnailWith((*cache_)[url].score_, score)))
return true; return true;
base::TimeTicks encode_start = base::TimeTicks::Now(); base::TimeTicks encode_start = base::TimeTicks::Now();
// Encode the SkBitmap to jpeg and add to cache. // Encode the SkBitmap to jpeg.
scoped_refptr<RefCountedBytes> jpeg_data = new RefCountedBytes; scoped_refptr<RefCountedBytes> jpeg_data = new RefCountedBytes;
SkAutoLockPixels thumbnail_lock(thumbnail); SkAutoLockPixels thumbnail_lock(thumbnail);
bool encoded = JPEGCodec::Encode( bool encoded = JPEGCodec::Encode(
...@@ -79,38 +84,31 @@ bool ThumbnailStore::SetPageThumbnail(const GURL& url, ...@@ -79,38 +84,31 @@ bool ThumbnailStore::SetPageThumbnail(const GURL& url,
return false; return false;
// Update the cache_ with the new thumbnail. // Update the cache_ with the new thumbnail.
(*cache_)[url] = std::make_pair(jpeg_data, score); (*cache_)[url] = CacheEntry(jpeg_data, score, true);
// 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,
jpeg_data, score));
}
return true; return true;
} }
bool ThumbnailStore::GetPageThumbnail( bool ThumbnailStore::GetPageThumbnail(
const GURL& url, const GURL& url,
RefCountedBytes** data) { RefCountedBytes** data) {
if (!cache_initialized_ || IsURLBlacklisted(url)) if (!cache_.get() || IsURLBlacklisted(url))
return false; return false;
// Look up the |url| in the redirect list to find the final destination // Look up the |url| in the redirect list to find the final destination
// which is the key into the |cache_|. // which is the key into the |cache_|.
history::RedirectMap::iterator it = redirect_urls_->find(url); history::RedirectMap::iterator it = redirect_urls_->find(url);
if (it == redirect_urls_->end()) if (it != redirect_urls_->end()) {
return false; // Return the first available thumbnail starting at the end of the
// redirect list.
// Return the first available thumbnail starting at the end of the history::RedirectList::reverse_iterator rit;
// redirect list. for (rit = it->second->data.rbegin();
history::RedirectList::reverse_iterator rit; rit != it->second->data.rend(); ++rit) {
for (rit = it->second->data.rbegin(); if (cache_->find(*rit) != cache_->end()) {
rit != it->second->data.rend(); ++rit) { *data = (*cache_)[*rit].data_.get();
if (cache_->find(*rit) != cache_->end()) { (*data)->AddRef();
*data = (*cache_)[*rit].first; return true;
(*data)->AddRef(); }
return true;
} }
} }
...@@ -119,7 +117,7 @@ bool ThumbnailStore::GetPageThumbnail( ...@@ -119,7 +117,7 @@ bool ThumbnailStore::GetPageThumbnail(
if (cache_->find(url) == cache_->end()) if (cache_->find(url) == cache_->end())
return false; return false;
*data = (*cache_)[url].first; *data = (*cache_)[url].data_.get();
(*data)->AddRef(); (*data)->AddRef();
return true; return true;
} }
...@@ -141,14 +139,14 @@ void ThumbnailStore::OnURLDataAvailable(std::vector<GURL>* urls, ...@@ -141,14 +139,14 @@ void ThumbnailStore::OnURLDataAvailable(std::vector<GURL>* urls,
} }
void ThumbnailStore::CleanCacheData() { void ThumbnailStore::CleanCacheData() {
if (!cache_initialized_) if (!cache_.get())
return; return;
// For each URL in the cache, search the RedirectMap for the originating URL. // For each URL in the cache, search the RedirectMap for the originating URL.
// If this URL is blacklisted or not in the most visited list, delete the // If this URL is blacklisted or not in the most visited list, delete the
// thumbnail data for it from the cache and from disk in the background. // thumbnail data for it from the cache and from disk in the background.
scoped_refptr<RefCountedVector<GURL> > old_urls = new RefCountedVector<GURL>; scoped_refptr<RefCountedVector<GURL> > old_urls = new RefCountedVector<GURL>;
for (ThumbnailStore::Cache::iterator cache_it = cache_->begin(); for (Cache::iterator cache_it = cache_->begin();
cache_it != cache_->end();) { cache_it != cache_->end();) {
const GURL* url = NULL; const GURL* url = NULL;
for (history::RedirectMap::iterator it = redirect_urls_->begin(); for (history::RedirectMap::iterator it = redirect_urls_->begin();
...@@ -171,145 +169,115 @@ void ThumbnailStore::CleanCacheData() { ...@@ -171,145 +169,115 @@ void ThumbnailStore::CleanCacheData() {
if (old_urls->data.size()) { if (old_urls->data.size()) {
g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
NewRunnableMethod(this, &ThumbnailStore::DeleteThumbnails, old_urls)); NewRunnableMethod(this, &ThumbnailStore::CommitCacheToDB, old_urls));
} }
} }
void ThumbnailStore::DeleteThumbnails( void ThumbnailStore::CommitCacheToDB(
scoped_refptr<RefCountedVector<GURL> > thumbnail_urls) const { scoped_refptr<RefCountedVector<GURL> > stale_urls) const {
for (std::vector<GURL>::iterator it = thumbnail_urls->data.begin(); if (!db_)
it != thumbnail_urls->data.end(); ++it) return;
file_util::Delete(file_path_.AppendASCII(MD5String(it->spec())), false);
}
void ThumbnailStore::GetAllThumbnailsFromDisk(FilePath filepath,
MessageLoop* cb_loop) {
ThumbnailStore::Cache* cache = new ThumbnailStore::Cache;
// Create the specified directory if it does not exist. // Delete old thumbnails.
if (!file_util::DirectoryExists(filepath)) { if (stale_urls.get()) {
file_util::CreateDirectory(filepath); for (std::vector<GURL>::iterator it = stale_urls->data.begin();
} else { it != stale_urls->data.end(); ++it) {
// Walk the directory and read the thumbnail data from disk. SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
FilePath path; "DELETE FROM thumbnails WHERE url=?");
GURL url; statement->bind_string(0, it->spec());
RefCountedBytes* data; if (statement->step() != SQLITE_DONE)
ThumbnailScore score; NOTREACHED();
file_util::FileEnumerator fenum(filepath, false,
file_util::FileEnumerator::FILES);
while (!(path = fenum.Next()).empty()) {
data = new RefCountedBytes;
if (GetPageThumbnailFromDisk(path, &url, data, &score))
(*cache)[url] = std::make_pair(data, score);
else
delete data;
} }
} }
cb_loop->PostTask(FROM_HERE,
NewRunnableMethod(this, &ThumbnailStore::OnDiskDataAvailable, cache));
}
bool ThumbnailStore::GetPageThumbnailFromDisk(const FilePath& file, // Update cached thumbnails.
GURL* url, for (Cache::iterator it = cache_->begin(); it != cache_->end(); ++it) {
RefCountedBytes* data, if (!it->second.dirty_)
ThumbnailScore* score) const { continue;
int64 file_size;
if (!file_util::GetFileSize(file, &file_size)) SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
return false; "INSERT OR REPLACE INTO thumbnails "
"(url, boring_score, good_clipping, at_top, time_taken, data) "
"VALUES (?,?,?,?,?,?)");
statement->bind_string(0, it->first.spec());
statement->bind_double(1, it->second.score_.boring_score);
statement->bind_bool(2, it->second.score_.good_clipping);
statement->bind_bool(3, it->second.score_.at_top);
statement->bind_int64(4, it->second.score_.time_at_snapshot.
ToInternalValue());
statement->bind_blob(5, &it->second.data_->data[0],
static_cast<int>(it->second.data_->data.size()));
if (statement->step() != SQLITE_DONE)
DLOG(WARNING) << "Unable to insert thumbnail for URL";
else
it->second.dirty_ = false;
}
}
// Read the file into a buffer. void ThumbnailStore::InitializeFromDB(const FilePath& db_name,
std::vector<char> file_data; MessageLoop* cb_loop) {
file_data.resize(static_cast<unsigned int>(file_size)); if (OpenSqliteDb(db_name, &db_) != SQLITE_OK)
if (file_util::ReadFile(file, &file_data[0], return;
static_cast<int>(file_size)) == -1)
return false;
// Unpack the url, ThumbnailScore and JPEG size from the buffer. // Use a large page size since the thumbnails we are storing are typically
std::string url_string; // large, a small cache size since we cache in memory and don't go to disk
unsigned int jpeg_len; // often, and take exclusive access since nobody else uses this db.
void* iter = NULL; sqlite3_exec(db_, "PRAGMA page_size=4096 "
Pickle packed(&file_data[0], static_cast<int>(file_size)); "PRAGMA cache_size=64 "
"PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, NULL);
statement_cache_ = new SqliteStatementCache;
// Use local DBCloseScoper so that if we cannot create the table and
// need to return, the |db_| and |statement_cache_| are closed properly.
history::DBCloseScoper scoper(&db_, &statement_cache_);
if (!DoesSqliteTableExist(db_, "thumbnails")) {
if (sqlite3_exec(db_, "CREATE TABLE thumbnails ("
"url LONGVARCHAR PRIMARY KEY,"
"boring_score DOUBLE DEFAULT 1.0,"
"good_clipping INTEGER DEFAULT 0,"
"at_top INTEGER DEFAULT 0,"
"time_taken INTEGER DEFAULT 0,"
"data BLOB)", NULL, NULL, NULL) != SQLITE_OK)
return;
}
if (!packed.ReadString(&iter, &url_string) || statement_cache_->set_db(db_);
!UnpackScore(score, packed, iter) ||
!packed.ReadUInt32(&iter, &jpeg_len))
return false;
// Store the url to the out parameter. // Now we can use a DBCloseScoper at the object scope.
GURL temp_url(url_string); scoper.Detach();
url->Swap(&temp_url); close_scoper_.Attach(&db_, &statement_cache_);
// Unpack the JPEG data from the buffer. if (cb_loop)
const char* jpeg_data = NULL; GetAllThumbnailsFromDisk(cb_loop);
int out_len; }
if (!packed.ReadData(&iter, &jpeg_data, &out_len) || void ThumbnailStore::GetAllThumbnailsFromDisk(MessageLoop* cb_loop) {
out_len != static_cast<int>(jpeg_len)) ThumbnailStore::Cache* cache = new ThumbnailStore::Cache;
return false;
// Copy jpeg data to the out parameter. SQLITE_UNIQUE_STATEMENT(statement, *statement_cache_,
data->data.resize(jpeg_len); "SELECT * FROM thumbnails");
memcpy(&data->data[0], jpeg_data, jpeg_len);
while (statement->step() == SQLITE_ROW) {
GURL url(statement->column_string(0));
ThumbnailScore score(statement->column_double(1), // Boring score
statement->column_bool(2), // Good clipping
statement->column_bool(3), // At top
base::Time::FromInternalValue(
statement->column_int64(4))); // Time taken
scoped_refptr<RefCountedBytes> data = new RefCountedBytes;
if (statement->column_blob_as_vector(5, &data->data))
(*cache)[url] = CacheEntry(data, score, false);
}
return true; cb_loop->PostTask(FROM_HERE,
NewRunnableMethod(this, &ThumbnailStore::OnDiskDataAvailable, cache));
} }
void ThumbnailStore::OnDiskDataAvailable(ThumbnailStore::Cache* cache) { void ThumbnailStore::OnDiskDataAvailable(ThumbnailStore::Cache* cache) {
if (cache) { if (cache)
cache_.reset(cache); cache_.reset(cache);
cache_initialized_ = true;
}
}
bool ThumbnailStore::WriteThumbnailToDisk(const GURL& url,
scoped_refptr<RefCountedBytes> data,
const ThumbnailScore& score) const {
Pickle packed;
FilePath file = file_path_.AppendASCII(MD5String(url.spec()));
// Pack the url, ThumbnailScore, and the JPEG data.
packed.WriteString(url.spec());
PackScore(score, &packed);
packed.WriteUInt32(data->data.size());
packed.WriteData(reinterpret_cast<char*>(&data->data[0]), data->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;
}
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;
} }
bool ThumbnailStore::ShouldStoreThumbnailForURL(const GURL& url) const { bool ThumbnailStore::ShouldStoreThumbnailForURL(const GURL& url) const {
...@@ -333,6 +301,6 @@ std::wstring ThumbnailStore::GetDictionaryKeyForURL( ...@@ -333,6 +301,6 @@ std::wstring ThumbnailStore::GetDictionaryKeyForURL(
bool ThumbnailStore::IsPopular(const GURL& url) const { bool ThumbnailStore::IsPopular(const GURL& url) const {
return most_visited_urls_->end() != find(most_visited_urls_->begin(), return most_visited_urls_->end() != find(most_visited_urls_->begin(),
most_visited_urls_->end(), most_visited_urls_->end(),
url); url);
} }
...@@ -15,17 +15,19 @@ ...@@ -15,17 +15,19 @@
#include "base/timer.h" #include "base/timer.h"
#include "chrome/browser/cancelable_request.h" #include "chrome/browser/cancelable_request.h"
#include "chrome/browser/history/history.h" #include "chrome/browser/history/history.h"
#include "chrome/browser/history/url_database.h" // For DBCloseScoper
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/common/ref_counted_util.h" #include "chrome/common/ref_counted_util.h"
#include "chrome/common/sqlite_compiled_statement.h"
#include "chrome/common/thumbnail_score.h"
#include "testing/gtest/include/gtest/gtest_prod.h" #include "testing/gtest/include/gtest/gtest_prod.h"
class DictionaryValue; class DictionaryValue;
class GURL; class GURL;
class HistoryService; class HistoryService;
class Pickle;
class Profile; class Profile;
class SkBitmap; class SkBitmap;
struct ThumbnailScore; struct sqlite3;
namespace base { namespace base {
class Time; class Time;
} }
...@@ -38,17 +40,13 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { ...@@ -38,17 +40,13 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> {
~ThumbnailStore(); ~ThumbnailStore();
// Must be called after creation but before other methods are called. // Must be called after creation but before other methods are called.
// file_path is a directory where a new database should be created void Init(const FilePath& db_name, // The location of the database.
// or the location of an existing databse. Profile* profile); // To get to the HistoryService.
void Init(const FilePath& file_path, Profile* profile);
// Stores the given thumbnail and score with the associated url in the cache. // 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, bool SetPageThumbnail(const GURL& url,
const SkBitmap& thumbnail, const SkBitmap& thumbnail,
const ThumbnailScore& score, const ThumbnailScore& score);
bool write_to_disk);
// Sets *data to point to the thumbnail for the given url. // Sets *data to point to the thumbnail for the given url.
// Returns false if no thumbnail available. // Returns false if no thumbnail available.
...@@ -61,9 +59,22 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { ...@@ -61,9 +59,22 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> {
FRIEND_TEST(ThumbnailStoreTest, FollowRedirects); FRIEND_TEST(ThumbnailStoreTest, FollowRedirects);
friend class ThumbnailStoreTest; friend class ThumbnailStoreTest;
struct CacheEntry {
scoped_refptr<RefCountedBytes> data_;
ThumbnailScore score_;
bool dirty_;
CacheEntry() : data_(NULL), score_(ThumbnailScore()), dirty_(false) {}
CacheEntry(RefCountedBytes* data,
const ThumbnailScore& score,
bool dirty)
: data_(data),
score_(score),
dirty_(dirty) {}
};
// Data structure used to store thumbnail data in memory. // Data structure used to store thumbnail data in memory.
typedef std::map<GURL, std::pair<scoped_refptr<RefCountedBytes>, typedef std::map<GURL, CacheEntry> Cache;
ThumbnailScore> > Cache;
// Most visited URLs and their redirect lists ------------------------------- // Most visited URLs and their redirect lists -------------------------------
...@@ -72,8 +83,7 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { ...@@ -72,8 +83,7 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> {
// callback is OnURLDataAvailable. // callback is OnURLDataAvailable.
void UpdateURLData(); void UpdateURLData();
// The callback for UpdateURLData. The ThumbnailStore takes ownership of // The callback for UpdateURLData.
// the most visited urls list and redirect lists passed in.
void OnURLDataAvailable(std::vector<GURL>* urls, void OnURLDataAvailable(std::vector<GURL>* urls,
history::RedirectMap* redirects); history::RedirectMap* redirects);
...@@ -84,43 +94,26 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { ...@@ -84,43 +94,26 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> {
// visited sites. // visited sites.
void CleanCacheData(); void CleanCacheData();
// Deletes thumbnail data from disk for the given list of urls.
void DeleteThumbnails(
scoped_refptr<RefCountedVector<GURL> > thumbnail_urls) const;
// Disk operations ---------------------------------------------------------- // Disk operations ----------------------------------------------------------
// Initialize |db_| to the database specified in |db_name|. If |cb_loop|
// is non-null, calls GetAllThumbnailsFromDisk. Done on the file_thread.
void InitializeFromDB(const FilePath& db_name, MessageLoop* cb_loop);
// Read all thumbnail data from the specified FilePath into a Cache object. // Read all thumbnail data from the specified FilePath into a Cache object.
// Done on the file_thread and returns to OnDiskDataAvailable on the thread // Done on the file_thread and returns to OnDiskDataAvailable on the thread
// owning the specified MessageLoop. // owning the specified MessageLoop.
void GetAllThumbnailsFromDisk(FilePath filepath, MessageLoop* cb_loop); void GetAllThumbnailsFromDisk(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,
RefCountedBytes* data,
ThumbnailScore* score) const;
// Once thumbnail data from the disk is available from the file_thread, // 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 // 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. // Cache* passed in and retains this Cache* for the lifetime of the object.
void OnDiskDataAvailable(Cache* cache); void OnDiskDataAvailable(Cache* cache);
// Write thumbnail data to disk for a given url. // Delete each URL in the given vector from the DB and write all dirty
bool WriteThumbnailToDisk(const GURL& url, // cache entries to the DB.
scoped_refptr<RefCountedBytes> data, void CommitCacheToDB(
const ThumbnailScore& score) const; scoped_refptr<RefCountedVector<GURL> > stale_urls) const;
// Pack the given ThumbnailScore into the given Pickle.
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) const;
// Decide whether to store data --------------------------------------------- // Decide whether to store data ---------------------------------------------
...@@ -139,10 +132,11 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> { ...@@ -139,10 +132,11 @@ class ThumbnailStore : public base::RefCountedThreadSafe<ThumbnailStore> {
// The Cache maintained by the object. // The Cache maintained by the object.
scoped_ptr<Cache> cache_; scoped_ptr<Cache> cache_;
bool cache_initialized_;
// The location of the thumbnail store. // The database holding the thumbnails on disk.
FilePath file_path_; sqlite3* db_;
SqliteStatementCache* statement_cache_;
history::DBCloseScoper close_scoper_;
// We hold a reference to the history service to query for most visited URLs // We hold a reference to the history service to query for most visited URLs
// and redirect information. // and redirect information.
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include <string.h> #include <string.h>
#include <algorithm>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
...@@ -12,12 +13,13 @@ ...@@ -12,12 +13,13 @@
#include "base/file_path.h" #include "base/file_path.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/gfx/jpeg_codec.h" #include "base/gfx/jpeg_codec.h"
#include "base/md5.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/ref_counted.h" #include "base/ref_counted.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "chrome/common/ref_counted_util.h" #include "chrome/common/ref_counted_util.h"
#include "chrome/common/thumbnail_score.h" #include "chrome/common/thumbnail_score.h"
#include "chrome/common/sqlite_compiled_statement.h"
#include "chrome/common/sqlite_utils.h"
#include "chrome/tools/profiles/thumbnail-inl.h" #include "chrome/tools/profiles/thumbnail-inl.h"
#include "googleurl/src/gurl.h" #include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -30,9 +32,10 @@ inline unsigned int diff(unsigned int a, unsigned int b) { ...@@ -30,9 +32,10 @@ inline unsigned int diff(unsigned int a, unsigned int b) {
class ThumbnailStoreTest : public testing::Test { class ThumbnailStoreTest : public testing::Test {
public: public:
ThumbnailStoreTest() : score1_(.5, true, false), ThumbnailStoreTest() : score_(.5, true, false),
url1_("http://www.google.com/"), url2_("http://www.elgoog.com") { url_("http://www.google.com/") {
} }
~ThumbnailStoreTest() { ~ThumbnailStoreTest() {
} }
...@@ -40,33 +43,32 @@ class ThumbnailStoreTest : public testing::Test { ...@@ -40,33 +43,32 @@ class ThumbnailStoreTest : public testing::Test {
void SetUp(); void SetUp();
void TearDown() { void TearDown() {
file_util::Delete(file_path_.AppendASCII(url1_.host()), false); file_util::Delete(db_name_, false);
file_util::Delete(file_path_.AppendASCII(url2_.host()), false);
} }
// Compute the max difference over all pixels for each RGBA component. // Compute the max difference over all pixels for each RGBA component.
void PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b); void PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b);
// The directory where ThumbnailStore will store data. // The directory where ThumbnailStore will store data.
FilePath file_path_; FilePath db_name_;
scoped_refptr<ThumbnailStore> store_; scoped_refptr<ThumbnailStore> store_;
scoped_ptr<SkBitmap> google_; scoped_ptr<SkBitmap> google_;
scoped_ptr<SkBitmap> weewar_; scoped_ptr<SkBitmap> weewar_;
scoped_refptr<RefCountedBytes> jpeg_google_; scoped_refptr<RefCountedBytes> jpeg_google_;
scoped_refptr<RefCountedBytes> jpeg_weewar_; scoped_refptr<RefCountedBytes> jpeg_weewar_;
ThumbnailScore score1_; ThumbnailScore score_;
GURL url1_, url2_; GURL url_;
base::Time time_; base::Time time_;
}; };
void ThumbnailStoreTest::SetUp() { void ThumbnailStoreTest::SetUp() {
if (!file_util::GetTempDir(&file_path_)) if (!file_util::GetTempDir(&db_name_))
FAIL(); FAIL();
// Delete any old thumbnail files if they exist. // Delete any old thumbnail files if they exist.
file_util::Delete(file_path_.AppendASCII(url1_.host()), false); db_name_ = db_name_.AppendASCII("ThumbnailDB");
file_util::Delete(file_path_.AppendASCII(url2_.host()), false); file_util::Delete(db_name_, false);
google_.reset(JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail))); google_.reset(JPEGCodec::Decode(kGoogleThumbnail, sizeof(kGoogleThumbnail)));
weewar_.reset(JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail))); weewar_.reset(JPEGCodec::Decode(kWeewarThumbnail, sizeof(kWeewarThumbnail)));
...@@ -90,12 +92,12 @@ void ThumbnailStoreTest::SetUp() { ...@@ -90,12 +92,12 @@ void ThumbnailStoreTest::SetUp() {
&(jpeg_weewar_->data)); &(jpeg_weewar_->data));
store_ = new ThumbnailStore; store_ = new ThumbnailStore;
store_->cache_initialized_ = true;
store_->file_path_ = file_path_;
store_->most_visited_urls_.reset(new std::vector<GURL>);
store_->most_visited_urls_->push_back(url1_);
store_->cache_.reset(new ThumbnailStore::Cache); store_->cache_.reset(new ThumbnailStore::Cache);
store_->redirect_urls_.reset(new history::RedirectMap); store_->redirect_urls_.reset(new history::RedirectMap);
store_->most_visited_urls_.reset(new std::vector<GURL>);
store_->most_visited_urls_->push_back(url_);
} }
void ThumbnailStoreTest::PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b) { void ThumbnailStoreTest::PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b) {
...@@ -139,21 +141,14 @@ void ThumbnailStoreTest::PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b) { ...@@ -139,21 +141,14 @@ void ThumbnailStoreTest::PrintPixelDiff(SkBitmap* image_a, SkBitmap* image_b) {
TEST_F(ThumbnailStoreTest, UpdateThumbnail) { TEST_F(ThumbnailStoreTest, UpdateThumbnail) {
RefCountedBytes* read_image = NULL; RefCountedBytes* read_image = NULL;
ThumbnailScore score2(0.1, true, true); ThumbnailScore score2(0.1, true, true);
store_->cache_->clear();
store_->redirect_urls_->clear();
// store_ google_ with a low score, then weewar_ with a higher score // store_ google_ with a low score, then weewar_ with a higher score
// and check that weewar_ overwrote google_. // and check that weewar_ overwrote google_.
EXPECT_TRUE(store_->SetPageThumbnail(url1_, *google_, score1_, false)); EXPECT_TRUE(store_->SetPageThumbnail(url_, *google_, score_));
EXPECT_TRUE(store_->SetPageThumbnail(url1_, *weewar_, score2, false)); EXPECT_TRUE(store_->SetPageThumbnail(url_, *weewar_, score2));
// Set fake redirects list.
scoped_ptr<std::vector<GURL> > redirects(new std::vector<GURL>);
redirects->push_back(url1_);
(*store_->redirect_urls_)[url1_] = new RefCountedVector<GURL>(*redirects);
EXPECT_TRUE(store_->GetPageThumbnail(url1_, &read_image)); EXPECT_TRUE(store_->GetPageThumbnail(url_, &read_image));
EXPECT_EQ(read_image->data.size(), jpeg_weewar_->data.size()); EXPECT_EQ(read_image->data.size(), jpeg_weewar_->data.size());
EXPECT_EQ(0, memcmp(&read_image->data[0], &jpeg_weewar_->data[0], EXPECT_EQ(0, memcmp(&read_image->data[0], &jpeg_weewar_->data[0],
jpeg_weewar_->data.size())); jpeg_weewar_->data.size()));
...@@ -163,24 +158,16 @@ TEST_F(ThumbnailStoreTest, UpdateThumbnail) { ...@@ -163,24 +158,16 @@ TEST_F(ThumbnailStoreTest, UpdateThumbnail) {
TEST_F(ThumbnailStoreTest, RetrieveFromCache) { TEST_F(ThumbnailStoreTest, RetrieveFromCache) {
RefCountedBytes* read_image = NULL; RefCountedBytes* read_image = NULL;
store_->cache_->clear();
store_->redirect_urls_->clear();
// Retrieve a thumbnail/score for a page not in the cache. // Retrieve a thumbnail/score for a page not in the cache.
EXPECT_FALSE(store_->GetPageThumbnail(url2_, &read_image)); EXPECT_FALSE(store_->GetPageThumbnail(GURL("nonexistent"), &read_image));
// store_ a thumbnail into the cache and retrieve it.
EXPECT_TRUE(store_->SetPageThumbnail(url1_, *google_, score1_, false)); // Store a thumbnail into the cache and retrieve it.
// Set fake redirects list. EXPECT_TRUE(store_->SetPageThumbnail(url_, *google_, score_));
scoped_ptr<std::vector<GURL> > redirects(new std::vector<GURL>); EXPECT_TRUE(store_->GetPageThumbnail(url_, &read_image));
redirects->push_back(url1_); EXPECT_TRUE(score_.Equals((*store_->cache_)[url_].score_));
(*store_->redirect_urls_)[url1_] = new RefCountedVector<GURL>(*redirects);
EXPECT_TRUE(store_->GetPageThumbnail(url1_, &read_image));
EXPECT_TRUE(score1_.Equals((*store_->cache_)[url1_].second));
EXPECT_TRUE(read_image->data.size() == jpeg_google_->data.size()); EXPECT_TRUE(read_image->data.size() == jpeg_google_->data.size());
EXPECT_EQ(0, memcmp(&read_image->data[0], &jpeg_google_->data[0], EXPECT_EQ(0, memcmp(&read_image->data[0], &jpeg_google_->data[0],
jpeg_google_->data.size())); jpeg_google_->data.size()));
...@@ -189,46 +176,47 @@ TEST_F(ThumbnailStoreTest, RetrieveFromCache) { ...@@ -189,46 +176,47 @@ TEST_F(ThumbnailStoreTest, RetrieveFromCache) {
} }
TEST_F(ThumbnailStoreTest, RetrieveFromDisk) { TEST_F(ThumbnailStoreTest, RetrieveFromDisk) {
scoped_refptr<RefCountedBytes> read_image = new RefCountedBytes; EXPECT_TRUE(store_->SetPageThumbnail(url_, *google_, score_));
ThumbnailScore score2;
store_->cache_->clear(); // Write the thumbnail to disk and retrieve it.
store_->redirect_urls_->clear();
store_->InitializeFromDB(db_name_, NULL);
// store_ a thumbnail onto the disk and retrieve it. store_->CommitCacheToDB(NULL); // Write to the DB (dirty bit sould be set)
store_->cache_->clear(); // Clear it from the cache.
EXPECT_TRUE(store_->SetPageThumbnail(url1_, *google_, score1_, false));
EXPECT_TRUE(store_->WriteThumbnailToDisk(url1_, jpeg_google_, score1_)); // Read from the DB.
EXPECT_TRUE(store_->GetPageThumbnailFromDisk(file_path_.AppendASCII( SQLITE_UNIQUE_STATEMENT(statement, *store_->statement_cache_,
MD5String(url1_.spec())), &url2_, read_image, &score2)); "SELECT * FROM thumbnails");
EXPECT_TRUE(url1_ == url2_); EXPECT_TRUE(statement->step() == SQLITE_ROW);
EXPECT_TRUE(score1_.Equals(score2)); GURL url(statement->column_string(0));
EXPECT_TRUE(read_image->data.size() == jpeg_google_->data.size()); ThumbnailScore score(statement->column_double(1),
EXPECT_EQ(0, memcmp(&read_image->data[0], &jpeg_google_->data[0], statement->column_bool(2),
statement->column_bool(3),
base::Time::FromInternalValue(
statement->column_int64(4)));
scoped_refptr<RefCountedBytes> data = new RefCountedBytes;
EXPECT_TRUE(statement->column_blob_as_vector(5, &data->data));
EXPECT_TRUE(url == url_);
EXPECT_TRUE(score.Equals(score_));
EXPECT_TRUE(data->data.size() == jpeg_google_->data.size());
EXPECT_EQ(0, memcmp(&data->data[0], &jpeg_google_->data[0],
jpeg_google_->data.size())); jpeg_google_->data.size()));
} }
TEST_F(ThumbnailStoreTest, FollowRedirects) { TEST_F(ThumbnailStoreTest, FollowRedirects) {
RefCountedBytes* read_image = NULL; RefCountedBytes* read_image = NULL;
scoped_ptr<std::vector<GURL> > redirects(new std::vector<GURL>); std::vector<GURL> redirects;
store_->cache_->clear();
store_->redirect_urls_->clear();
GURL my_url("google"); GURL my_url("google");
redirects->push_back(GURL("google.com")); redirects.push_back(GURL("google.com"));
redirects->push_back(GURL("www.google.com")); redirects.push_back(GURL("www.google.com"));
redirects->push_back(url1_); // url1_ = http://www.google.com/ redirects.push_back(url_); // url_ = http://www.google.com/
(*store_->redirect_urls_)[my_url] = new RefCountedVector<GURL>(redirects);
store_->most_visited_urls_->push_back(my_url); store_->most_visited_urls_->push_back(my_url);
(*store_->redirect_urls_)[my_url] = new RefCountedVector<GURL>(*redirects); EXPECT_TRUE(store_->SetPageThumbnail(GURL("google.com"), *google_, score_));
EXPECT_TRUE(store_->SetPageThumbnail(url1_, *google_, score1_, false));
EXPECT_TRUE(store_->GetPageThumbnail(my_url, &read_image));
read_image->Release();
store_->cache_->erase(store_->cache_->find(url1_));
EXPECT_TRUE(store_->SetPageThumbnail(GURL("google.com"), *google_, score1_,
false));
EXPECT_TRUE(store_->GetPageThumbnail(my_url, &read_image)); EXPECT_TRUE(store_->GetPageThumbnail(my_url, &read_image));
read_image->Release(); read_image->Release();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment