This source file includes following definitions.
- GenerateSalt
- AsyncOpen
- WriteToFile
- AsyncWrite
- AsyncTruncate
- AsyncClose
- TableBuilder
- persist_to_disk_
- persist_to_disk_
- InitMembers
- Init
- TryToAddURL
- PostIOTask
- AddURL
- AddURLs
- DeleteAllURLs
- GetDelegate
- DeleteURLs
- AddFingerprint
- DeleteFingerprintsFromCurrentTable
- DeleteFingerprint
- WriteFullTable
- InitFromFile
- InitFromScratch
- ReadFileHeader
- GetDatabaseFileName
- CreateURLTable
- BeginReplaceURLTable
- FreeURLTable
- ResizeTableIfNecessary
- ResizeTable
- NewTableSizeForCount
- RebuildTableFromDelegate
- OnTableRebuildComplete
- WriteToFile
- WriteUsedItemCountToFile
- WriteHashRangeToFile
- ReadFromFile
- success_
- DisownMaster
- OnURL
- OnComplete
- OnCompleteMainThread
#include "components/visitedlink/browser/visitedlink_master.h"
#if defined(OS_WIN)
#include <windows.h>
#include <io.h>
#include <shlobj.h>
#endif
#include <stdio.h>
#include <algorithm>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/stack_container.h"
#include "base/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "components/visitedlink/browser/visitedlink_delegate.h"
#include "components/visitedlink/browser/visitedlink_event_listener.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
using content::BrowserThread;
namespace visitedlink {
const int32 VisitedLinkMaster::kFileHeaderSignatureOffset = 0;
const int32 VisitedLinkMaster::kFileHeaderVersionOffset = 4;
const int32 VisitedLinkMaster::kFileHeaderLengthOffset = 8;
const int32 VisitedLinkMaster::kFileHeaderUsedOffset = 12;
const int32 VisitedLinkMaster::kFileHeaderSaltOffset = 16;
const int32 VisitedLinkMaster::kFileCurrentVersion = 3;
const int32 VisitedLinkMaster::kFileSignature = 0x6b6e4c56;
const size_t VisitedLinkMaster::kFileHeaderSize =
kFileHeaderSaltOffset + LINK_SALT_LENGTH;
const unsigned VisitedLinkMaster::kDefaultTableSize = 16381;
const size_t VisitedLinkMaster::kBigDeleteThreshold = 64;
namespace {
void GenerateSalt(uint8 salt[LINK_SALT_LENGTH]) {
DCHECK_EQ(LINK_SALT_LENGTH, 8) << "This code assumes the length of the salt";
uint64 randval = base::RandUint64();
memcpy(salt, &randval, 8);
}
void AsyncOpen(FILE** file, const base::FilePath& filename) {
*file = base::OpenFile(filename, "wb+");
DLOG_IF(ERROR, !(*file)) << "Failed to open file " << filename.value();
}
static bool WriteToFile(FILE* file,
off_t offset,
const void* data,
size_t data_len) {
if (fseek(file, offset, SEEK_SET) != 0)
return false;
size_t num_written = fwrite(data, 1, data_len, file);
int ret = fflush(file);
DCHECK_EQ(0, ret);
return num_written == data_len;
}
void AsyncWrite(FILE** file, int32 offset, const std::string& data) {
if (*file)
WriteToFile(*file, offset, data.data(), data.size());
}
void AsyncTruncate(FILE** file) {
if (*file)
base::IgnoreResult(base::TruncateFile(*file));
}
void AsyncClose(FILE** file) {
if (*file)
base::IgnoreResult(fclose(*file));
free(file);
}
}
class VisitedLinkMaster::TableBuilder
: public VisitedLinkDelegate::URLEnumerator {
public:
TableBuilder(VisitedLinkMaster* master,
const uint8 salt[LINK_SALT_LENGTH]);
void DisownMaster();
virtual void OnURL(const GURL& url) OVERRIDE;
virtual void OnComplete(bool succeed) OVERRIDE;
private:
virtual ~TableBuilder() {}
void OnCompleteMainThread();
VisitedLinkMaster* master_;
bool success_;
uint8 salt_[LINK_SALT_LENGTH];
VisitedLinkCommon::Fingerprints fingerprints_;
DISALLOW_COPY_AND_ASSIGN(TableBuilder);
};
VisitedLinkMaster::VisitedLinkMaster(content::BrowserContext* browser_context,
VisitedLinkDelegate* delegate,
bool persist_to_disk)
: browser_context_(browser_context),
delegate_(delegate),
listener_(new VisitedLinkEventListener(this, browser_context)),
persist_to_disk_(persist_to_disk) {
InitMembers();
}
VisitedLinkMaster::VisitedLinkMaster(Listener* listener,
VisitedLinkDelegate* delegate,
bool persist_to_disk,
bool suppress_rebuild,
const base::FilePath& filename,
int32 default_table_size)
: browser_context_(NULL),
delegate_(delegate),
persist_to_disk_(persist_to_disk) {
listener_.reset(listener);
DCHECK(listener_.get());
InitMembers();
database_name_override_ = filename;
table_size_override_ = default_table_size;
suppress_rebuild_ = suppress_rebuild;
}
VisitedLinkMaster::~VisitedLinkMaster() {
if (table_builder_.get()) {
table_builder_->DisownMaster();
}
FreeURLTable();
}
void VisitedLinkMaster::InitMembers() {
file_ = NULL;
shared_memory_ = NULL;
shared_memory_serial_ = 0;
used_items_ = 0;
table_size_override_ = 0;
suppress_rebuild_ = false;
sequence_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken();
#ifndef NDEBUG
posted_asynchronous_operation_ = false;
#endif
}
bool VisitedLinkMaster::Init() {
base::ThreadRestrictions::ScopedAllowIO allow_io;
if (persist_to_disk_) {
if (InitFromFile())
return true;
}
return InitFromScratch(suppress_rebuild_);
}
VisitedLinkMaster::Hash VisitedLinkMaster::TryToAddURL(const GURL& url) {
if (browser_context_ && browser_context_->IsOffTheRecord()) {
NOTREACHED();
return null_hash_;
}
if (!url.is_valid())
return null_hash_;
Fingerprint fingerprint = ComputeURLFingerprint(url.spec().data(),
url.spec().size(),
salt_);
if (table_builder_.get()) {
std::set<Fingerprint>::iterator found =
deleted_since_rebuild_.find(fingerprint);
if (found != deleted_since_rebuild_.end())
deleted_since_rebuild_.erase(found);
added_since_rebuild_.insert(fingerprint);
}
if (used_items_ / 8 > table_length_ / 10)
return null_hash_;
return AddFingerprint(fingerprint, true);
}
void VisitedLinkMaster::PostIOTask(const tracked_objects::Location& from_here,
const base::Closure& task) {
DCHECK(persist_to_disk_);
BrowserThread::GetBlockingPool()->PostSequencedWorkerTask(sequence_token_,
from_here, task);
}
void VisitedLinkMaster::AddURL(const GURL& url) {
Hash index = TryToAddURL(url);
if (!table_builder_.get() && index != null_hash_) {
if (persist_to_disk_) {
WriteUsedItemCountToFile();
WriteHashRangeToFile(index, index);
}
ResizeTableIfNecessary();
}
}
void VisitedLinkMaster::AddURLs(const std::vector<GURL>& url) {
for (std::vector<GURL>::const_iterator i = url.begin();
i != url.end(); ++i) {
Hash index = TryToAddURL(*i);
if (!table_builder_.get() && index != null_hash_)
ResizeTableIfNecessary();
}
if (!table_builder_.get() && persist_to_disk_)
WriteFullTable();
}
void VisitedLinkMaster::DeleteAllURLs() {
added_since_rebuild_.clear();
deleted_since_rebuild_.clear();
used_items_ = 0;
memset(hash_table_, 0, this->table_length_ * sizeof(Fingerprint));
if (!ResizeTableIfNecessary() && persist_to_disk_)
WriteFullTable();
listener_->Reset();
}
VisitedLinkDelegate* VisitedLinkMaster::GetDelegate() {
return delegate_;
}
void VisitedLinkMaster::DeleteURLs(URLIterator* urls) {
if (!urls->HasNextURL())
return;
listener_->Reset();
if (table_builder_.get()) {
while (urls->HasNextURL()) {
const GURL& url(urls->NextURL());
if (!url.is_valid())
continue;
Fingerprint fingerprint =
ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_);
deleted_since_rebuild_.insert(fingerprint);
std::set<Fingerprint>::iterator found =
added_since_rebuild_.find(fingerprint);
if (found != added_since_rebuild_.end())
added_since_rebuild_.erase(found);
DeleteFingerprint(fingerprint, false);
}
return;
}
std::set<Fingerprint> deleted_fingerprints;
while (urls->HasNextURL()) {
const GURL& url(urls->NextURL());
if (!url.is_valid())
continue;
deleted_fingerprints.insert(
ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_));
}
DeleteFingerprintsFromCurrentTable(deleted_fingerprints);
}
VisitedLinkMaster::Hash VisitedLinkMaster::AddFingerprint(
Fingerprint fingerprint,
bool send_notifications) {
if (!hash_table_ || table_length_ == 0) {
NOTREACHED();
return null_hash_;
}
Hash cur_hash = HashFingerprint(fingerprint);
Hash first_hash = cur_hash;
while (true) {
Fingerprint cur_fingerprint = FingerprintAt(cur_hash);
if (cur_fingerprint == fingerprint)
return null_hash_;
if (cur_fingerprint == null_fingerprint_) {
hash_table_[cur_hash] = fingerprint;
used_items_++;
if (send_notifications)
listener_->Add(fingerprint);
return cur_hash;
}
cur_hash = IncrementHash(cur_hash);
if (cur_hash == first_hash) {
NOTREACHED();
return null_hash_;
}
}
}
void VisitedLinkMaster::DeleteFingerprintsFromCurrentTable(
const std::set<Fingerprint>& fingerprints) {
bool bulk_write = (fingerprints.size() > kBigDeleteThreshold);
for (std::set<Fingerprint>::const_iterator i = fingerprints.begin();
i != fingerprints.end(); ++i)
DeleteFingerprint(*i, !bulk_write);
if (ResizeTableIfNecessary())
return;
if (bulk_write && persist_to_disk_)
WriteFullTable();
}
bool VisitedLinkMaster::DeleteFingerprint(Fingerprint fingerprint,
bool update_file) {
if (!hash_table_ || table_length_ == 0) {
NOTREACHED();
return false;
}
if (!IsVisited(fingerprint))
return false;
used_items_--;
if (update_file && persist_to_disk_)
WriteUsedItemCountToFile();
Hash deleted_hash = HashFingerprint(fingerprint);
Hash end_range = deleted_hash;
while (true) {
Hash next_hash = IncrementHash(end_range);
if (next_hash == deleted_hash)
break;
if (!hash_table_[next_hash])
break;
end_range = next_hash;
}
base::StackVector<Fingerprint, 32> shuffled_fingerprints;
Hash stop_loop = IncrementHash(end_range);
for (Hash i = deleted_hash; i != stop_loop; i = IncrementHash(i)) {
if (hash_table_[i] != fingerprint) {
shuffled_fingerprints->push_back(hash_table_[i]);
used_items_--;
}
hash_table_[i] = null_fingerprint_;
}
if (!shuffled_fingerprints->empty()) {
for (size_t i = 0; i < shuffled_fingerprints->size(); i++)
AddFingerprint(shuffled_fingerprints[i], false);
}
if (update_file && persist_to_disk_)
WriteHashRangeToFile(deleted_hash, end_range);
return true;
}
void VisitedLinkMaster::WriteFullTable() {
DCHECK(persist_to_disk_);
if (!file_) {
file_ = static_cast<FILE**>(calloc(1, sizeof(*file_)));
base::FilePath filename;
GetDatabaseFileName(&filename);
PostIOTask(FROM_HERE, base::Bind(&AsyncOpen, file_, filename));
}
int32 header[4];
header[0] = kFileSignature;
header[1] = kFileCurrentVersion;
header[2] = table_length_;
header[3] = used_items_;
WriteToFile(file_, 0, header, sizeof(header));
WriteToFile(file_, sizeof(header), salt_, LINK_SALT_LENGTH);
WriteToFile(file_, kFileHeaderSize,
hash_table_, table_length_ * sizeof(Fingerprint));
PostIOTask(FROM_HERE, base::Bind(&AsyncTruncate, file_));
}
bool VisitedLinkMaster::InitFromFile() {
DCHECK(file_ == NULL);
DCHECK(persist_to_disk_);
base::FilePath filename;
GetDatabaseFileName(&filename);
base::ScopedFILE file_closer(base::OpenFile(filename, "rb+"));
if (!file_closer.get())
return false;
int32 num_entries, used_count;
if (!ReadFileHeader(file_closer.get(), &num_entries, &used_count, salt_))
return false;
if (!CreateURLTable(num_entries, false))
return false;
if (!ReadFromFile(file_closer.get(), kFileHeaderSize,
hash_table_, num_entries * sizeof(Fingerprint))) {
FreeURLTable();
return false;
}
used_items_ = used_count;
#ifndef NDEBUG
DebugValidate();
#endif
file_ = static_cast<FILE**>(malloc(sizeof(*file_)));
*file_ = file_closer.release();
return true;
}
bool VisitedLinkMaster::InitFromScratch(bool suppress_rebuild) {
int32 table_size = kDefaultTableSize;
if (table_size_override_)
table_size = table_size_override_;
GenerateSalt(salt_);
if (!CreateURLTable(table_size, true))
return false;
#ifndef NDEBUG
DebugValidate();
#endif
if (suppress_rebuild && persist_to_disk_) {
WriteFullTable();
return true;
}
return RebuildTableFromDelegate();
}
bool VisitedLinkMaster::ReadFileHeader(FILE* file,
int32* num_entries,
int32* used_count,
uint8 salt[LINK_SALT_LENGTH]) {
DCHECK(persist_to_disk_);
if (fseek(file, 0, SEEK_END) == -1)
return false;
size_t file_size = ftell(file);
if (file_size <= kFileHeaderSize)
return false;
uint8 header[kFileHeaderSize];
if (!ReadFromFile(file, 0, &header, kFileHeaderSize))
return false;
int32 signature;
memcpy(&signature, &header[kFileHeaderSignatureOffset], sizeof(signature));
if (signature != kFileSignature)
return false;
int32 version;
memcpy(&version, &header[kFileHeaderVersionOffset], sizeof(version));
if (version != kFileCurrentVersion)
return false;
memcpy(num_entries, &header[kFileHeaderLengthOffset], sizeof(*num_entries));
if (*num_entries * sizeof(Fingerprint) + kFileHeaderSize != file_size)
return false;
memcpy(used_count, &header[kFileHeaderUsedOffset], sizeof(*used_count));
if (*used_count > *num_entries)
return false;
memcpy(salt, &header[kFileHeaderSaltOffset], LINK_SALT_LENGTH);
return true;
}
bool VisitedLinkMaster::GetDatabaseFileName(base::FilePath* filename) {
if (!database_name_override_.empty()) {
*filename = database_name_override_;
return true;
}
if (!browser_context_ || browser_context_->GetPath().empty())
return false;
base::FilePath profile_dir = browser_context_->GetPath();
*filename = profile_dir.Append(FILE_PATH_LITERAL("Visited Links"));
return true;
}
bool VisitedLinkMaster::CreateURLTable(int32 num_entries, bool init_to_empty) {
uint32 alloc_size = num_entries * sizeof(Fingerprint) + sizeof(SharedHeader);
shared_memory_ = new base::SharedMemory();
if (!shared_memory_)
return false;
if (!shared_memory_->CreateAndMapAnonymous(alloc_size)) {
delete shared_memory_;
shared_memory_ = NULL;
return false;
}
if (init_to_empty) {
memset(shared_memory_->memory(), 0, alloc_size);
used_items_ = 0;
}
table_length_ = num_entries;
SharedHeader* header = static_cast<SharedHeader*>(shared_memory_->memory());
header->length = table_length_;
memcpy(header->salt, salt_, LINK_SALT_LENGTH);
hash_table_ = reinterpret_cast<Fingerprint*>(
static_cast<char*>(shared_memory_->memory()) + sizeof(SharedHeader));
return true;
}
bool VisitedLinkMaster::BeginReplaceURLTable(int32 num_entries) {
base::SharedMemory *old_shared_memory = shared_memory_;
Fingerprint* old_hash_table = hash_table_;
int32 old_table_length = table_length_;
if (!CreateURLTable(num_entries, true)) {
shared_memory_ = old_shared_memory;
hash_table_ = old_hash_table;
table_length_ = old_table_length;
return false;
}
#ifndef NDEBUG
DebugValidate();
#endif
return true;
}
void VisitedLinkMaster::FreeURLTable() {
if (shared_memory_) {
delete shared_memory_;
shared_memory_ = NULL;
}
if (!persist_to_disk_ || !file_)
return;
PostIOTask(FROM_HERE, base::Bind(&AsyncClose, file_));
file_ = NULL;
}
bool VisitedLinkMaster::ResizeTableIfNecessary() {
DCHECK(table_length_ > 0) << "Must have a table";
const float max_table_load = 0.5f;
const float min_table_load = 0.2f;
float load = ComputeTableLoad();
if (load < max_table_load &&
(table_length_ <= static_cast<float>(kDefaultTableSize) ||
load > min_table_load))
return false;
int new_size = NewTableSizeForCount(used_items_);
DCHECK(new_size > used_items_);
DCHECK(load <= min_table_load || new_size > table_length_);
ResizeTable(new_size);
return true;
}
void VisitedLinkMaster::ResizeTable(int32 new_size) {
DCHECK(shared_memory_ && shared_memory_->memory() && hash_table_);
shared_memory_serial_++;
#ifndef NDEBUG
DebugValidate();
#endif
base::SharedMemory* old_shared_memory = shared_memory_;
Fingerprint* old_hash_table = hash_table_;
int32 old_table_length = table_length_;
if (!BeginReplaceURLTable(new_size))
return;
for (int32 i = 0; i < old_table_length; i++) {
Fingerprint cur = old_hash_table[i];
if (cur)
AddFingerprint(cur, false);
}
delete old_shared_memory;
listener_->NewTable(shared_memory_);
#ifndef NDEBUG
DebugValidate();
#endif
if (persist_to_disk_)
WriteFullTable();
}
uint32 VisitedLinkMaster::NewTableSizeForCount(int32 item_count) const {
static const int table_sizes[] = {
16381,
32767,
65521,
130051,
262127,
524269,
1048549,
2097143,
4194301,
8388571,
16777199,
33554347};
int desired = item_count * 3;
for (size_t i = 0; i < arraysize(table_sizes); i ++) {
if (table_sizes[i] > desired)
return table_sizes[i];
}
return item_count * 2 - 1;
}
bool VisitedLinkMaster::RebuildTableFromDelegate() {
DCHECK(!table_builder_.get());
table_builder_ = new TableBuilder(this, salt_);
delegate_->RebuildTable(table_builder_);
return true;
}
void VisitedLinkMaster::OnTableRebuildComplete(
bool success,
const std::vector<Fingerprint>& fingerprints) {
if (success) {
shared_memory_serial_++;
base::SharedMemory* old_shared_memory = shared_memory_;
int new_table_size = NewTableSizeForCount(
static_cast<int>(fingerprints.size() + added_since_rebuild_.size()));
if (BeginReplaceURLTable(new_table_size)) {
delete old_shared_memory;
for (size_t i = 0; i < fingerprints.size(); i++)
AddFingerprint(fingerprints[i], false);
for (std::set<Fingerprint>::iterator i = added_since_rebuild_.begin();
i != added_since_rebuild_.end(); ++i)
AddFingerprint(*i, false);
added_since_rebuild_.clear();
DeleteFingerprintsFromCurrentTable(deleted_since_rebuild_);
deleted_since_rebuild_.clear();
listener_->NewTable(shared_memory_);
if (persist_to_disk_)
WriteFullTable();
}
}
table_builder_ = NULL;
if (!rebuild_complete_task_.is_null()) {
rebuild_complete_task_.Run();
rebuild_complete_task_.Reset();
}
}
void VisitedLinkMaster::WriteToFile(FILE** file,
off_t offset,
void* data,
int32 data_size) {
DCHECK(persist_to_disk_);
#ifndef NDEBUG
posted_asynchronous_operation_ = true;
#endif
PostIOTask(FROM_HERE,
base::Bind(&AsyncWrite, file, offset,
std::string(static_cast<const char*>(data), data_size)));
}
void VisitedLinkMaster::WriteUsedItemCountToFile() {
DCHECK(persist_to_disk_);
if (!file_)
return;
WriteToFile(file_, kFileHeaderUsedOffset, &used_items_, sizeof(used_items_));
}
void VisitedLinkMaster::WriteHashRangeToFile(Hash first_hash, Hash last_hash) {
DCHECK(persist_to_disk_);
if (!file_)
return;
if (last_hash < first_hash) {
WriteToFile(file_, first_hash * sizeof(Fingerprint) + kFileHeaderSize,
&hash_table_[first_hash],
(table_length_ - first_hash + 1) * sizeof(Fingerprint));
WriteToFile(file_, kFileHeaderSize, hash_table_,
(last_hash + 1) * sizeof(Fingerprint));
} else {
WriteToFile(file_, first_hash * sizeof(Fingerprint) + kFileHeaderSize,
&hash_table_[first_hash],
(last_hash - first_hash + 1) * sizeof(Fingerprint));
}
}
bool VisitedLinkMaster::ReadFromFile(FILE* file,
off_t offset,
void* data,
size_t data_size) {
DCHECK(persist_to_disk_);
#ifndef NDEBUG
DCHECK(!posted_asynchronous_operation_);
#endif
if (fseek(file, offset, SEEK_SET) != 0)
return false;
size_t num_read = fread(data, 1, data_size, file);
return num_read == data_size;
}
VisitedLinkMaster::TableBuilder::TableBuilder(
VisitedLinkMaster* master,
const uint8 salt[LINK_SALT_LENGTH])
: master_(master),
success_(true) {
fingerprints_.reserve(4096);
memcpy(salt_, salt, LINK_SALT_LENGTH * sizeof(uint8));
}
void VisitedLinkMaster::TableBuilder::DisownMaster() {
master_ = NULL;
}
void VisitedLinkMaster::TableBuilder::OnURL(const GURL& url) {
if (!url.is_empty()) {
fingerprints_.push_back(VisitedLinkMaster::ComputeURLFingerprint(
url.spec().data(), url.spec().length(), salt_));
}
}
void VisitedLinkMaster::TableBuilder::OnComplete(bool success) {
success_ = success;
DLOG_IF(WARNING, !success) << "Unable to rebuild visited links";
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&TableBuilder::OnCompleteMainThread, this));
}
void VisitedLinkMaster::TableBuilder::OnCompleteMainThread() {
if (master_)
master_->OnTableRebuildComplete(success_, fingerprints_);
}
}