| Index: content/browser/indexed_db/indexed_db_backing_store.cc
|
| diff --git a/content/browser/indexed_db/indexed_db_backing_store.cc b/content/browser/indexed_db/indexed_db_backing_store.cc
|
| index d79407aec9d809ca91a8faf2b8da9ee8c1ee63e5..61eea103e61752ce5dfb49466fdce55d2f51a022 100644
|
| --- a/content/browser/indexed_db/indexed_db_backing_store.cc
|
| +++ b/content/browser/indexed_db/indexed_db_backing_store.cc
|
| @@ -6,11 +6,13 @@
|
|
|
| #include "base/file_util.h"
|
| #include "base/files/file_path.h"
|
| +#include "base/format_macros.h"
|
| #include "base/json/json_reader.h"
|
| #include "base/json/json_writer.h"
|
| #include "base/logging.h"
|
| #include "base/metrics/histogram.h"
|
| #include "base/strings/string_util.h"
|
| +#include "base/strings/stringprintf.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| #include "content/browser/child_process_security_policy_impl.h"
|
| #include "content/browser/indexed_db/indexed_db_blob_info.h"
|
| @@ -26,18 +28,53 @@
|
| #include "content/common/indexed_db/indexed_db_key.h"
|
| #include "content/common/indexed_db/indexed_db_key_path.h"
|
| #include "content/common/indexed_db/indexed_db_key_range.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "net/url_request/url_request_context.h"
|
| #include "third_party/WebKit/public/platform/WebIDBTypes.h"
|
| #include "third_party/WebKit/public/web/WebSerializedScriptValueVersion.h"
|
| #include "third_party/leveldatabase/env_chromium.h"
|
| #include "webkit/browser/blob/blob_data_handle.h"
|
| +#include "webkit/browser/fileapi/file_stream_writer.h"
|
| +#include "webkit/browser/fileapi/file_writer_delegate.h"
|
| +#include "webkit/browser/fileapi/local_file_stream_writer.h"
|
| #include "webkit/common/database/database_identifier.h"
|
|
|
| +using base::FilePath;
|
| using base::StringPiece;
|
| +using fileapi::FileWriterDelegate;
|
|
|
| namespace content {
|
|
|
| namespace {
|
|
|
| +FilePath GetBlobDirectoryName(const FilePath& pathBase, int64 database_id) {
|
| + return pathBase.AppendASCII(base::StringPrintf("%" PRIx64, database_id));
|
| +}
|
| +
|
| +FilePath GetBlobDirectoryNameForKey(const FilePath& pathBase,
|
| + int64 database_id,
|
| + int64 key) {
|
| + FilePath path = GetBlobDirectoryName(pathBase, database_id);
|
| + path = path.AppendASCII(base::StringPrintf(
|
| + "%02x", static_cast<int>(key & 0x000000000000ff00) >> 8));
|
| + return path;
|
| +}
|
| +
|
| +FilePath GetBlobFileNameForKey(const FilePath& pathBase,
|
| + int64 database_id,
|
| + int64 key) {
|
| + FilePath path = GetBlobDirectoryNameForKey(pathBase, database_id, key);
|
| + path = path.AppendASCII(base::StringPrintf("%" PRIx64, key));
|
| + return path;
|
| +}
|
| +
|
| +bool MakeIDBBlobDirectory(const FilePath& pathBase,
|
| + int64 database_id,
|
| + int64 key) {
|
| + FilePath path = GetBlobDirectoryNameForKey(pathBase, database_id, key);
|
| + return base::CreateDirectory(path);
|
| +}
|
| +
|
| static std::string ComputeOriginIdentifier(const GURL& origin_url) {
|
| return webkit_database::GetIdentifierFromOrigin(origin_url) + "@1";
|
| }
|
| @@ -441,6 +478,7 @@ IndexedDBBackingStore::IndexedDBBackingStore(
|
| IndexedDBFactory* indexed_db_factory,
|
| const GURL& origin_url,
|
| const base::FilePath& blob_path,
|
| + net::URLRequestContext* request_context,
|
| scoped_ptr<LevelDBDatabase> db,
|
| scoped_ptr<LevelDBComparator> comparator,
|
| base::TaskRunner* task_runner)
|
| @@ -448,6 +486,7 @@ IndexedDBBackingStore::IndexedDBBackingStore(
|
| origin_url_(origin_url),
|
| blob_path_(blob_path),
|
| origin_identifier_(ComputeOriginIdentifier(origin_url)),
|
| + request_context_(request_context),
|
| task_runner_(task_runner),
|
| db_(db.Pass()),
|
| comparator_(comparator.Pass()),
|
| @@ -506,6 +545,7 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
|
| IndexedDBFactory* indexed_db_factory,
|
| const GURL& origin_url,
|
| const base::FilePath& path_base,
|
| + net::URLRequestContext* request_context,
|
| blink::WebIDBDataLoss* data_loss,
|
| std::string* data_loss_message,
|
| bool* disk_full,
|
| @@ -515,6 +555,7 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
|
| return IndexedDBBackingStore::Open(indexed_db_factory,
|
| origin_url,
|
| path_base,
|
| + request_context,
|
| data_loss,
|
| data_loss_message,
|
| disk_full,
|
| @@ -655,6 +696,7 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
|
| IndexedDBFactory* indexed_db_factory,
|
| const GURL& origin_url,
|
| const base::FilePath& path_base,
|
| + net::URLRequestContext* request_context,
|
| blink::WebIDBDataLoss* data_loss,
|
| std::string* data_loss_message,
|
| bool* is_disk_full,
|
| @@ -780,6 +822,7 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Open(
|
| return Create(indexed_db_factory,
|
| origin_url,
|
| blob_path,
|
| + request_context,
|
| db.Pass(),
|
| comparator.Pass(),
|
| task_runner);
|
| @@ -815,6 +858,7 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::OpenInMemory(
|
| return Create(NULL /* indexed_db_factory */,
|
| origin_url,
|
| base::FilePath(),
|
| + NULL /* request_context */,
|
| db.Pass(),
|
| comparator.Pass(),
|
| task_runner);
|
| @@ -825,15 +869,16 @@ scoped_refptr<IndexedDBBackingStore> IndexedDBBackingStore::Create(
|
| IndexedDBFactory* indexed_db_factory,
|
| const GURL& origin_url,
|
| const base::FilePath& blob_path,
|
| + net::URLRequestContext* request_context,
|
| scoped_ptr<LevelDBDatabase> db,
|
| scoped_ptr<LevelDBComparator> comparator,
|
| base::TaskRunner* task_runner) {
|
| // TODO(jsbell): Handle comparator name changes.
|
| -
|
| scoped_refptr<IndexedDBBackingStore> backing_store(
|
| new IndexedDBBackingStore(indexed_db_factory,
|
| origin_url,
|
| blob_path,
|
| + request_context,
|
| db.Pass(),
|
| comparator.Pass(),
|
| task_runner));
|
| @@ -1693,6 +1738,204 @@ leveldb::Status IndexedDBBackingStore::KeyExistsInObjectStore(
|
| return s;
|
| }
|
|
|
| +class IndexedDBBackingStore::Transaction::ChainedBlobWriterImpl
|
| + : public IndexedDBBackingStore::Transaction::ChainedBlobWriter {
|
| + public:
|
| + typedef IndexedDBBackingStore::Transaction::WriteDescriptorVec
|
| + WriteDescriptorVec;
|
| + ChainedBlobWriterImpl(
|
| + int64 database_id,
|
| + IndexedDBBackingStore* backingStore,
|
| + WriteDescriptorVec& blobs,
|
| + scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback)
|
| + : waiting_for_callback_(false),
|
| + database_id_(database_id),
|
| + backing_store_(backingStore),
|
| + callback_(callback),
|
| + aborted_(false) {
|
| + blobs_.swap(blobs);
|
| + iter_ = blobs_.begin();
|
| + WriteNextFile();
|
| + }
|
| +
|
| + virtual void set_delegate(scoped_ptr<FileWriterDelegate> delegate) OVERRIDE {
|
| + delegate_.reset(delegate.release());
|
| + }
|
| +
|
| + virtual void ReportWriteCompletion(bool succeeded,
|
| + int64 bytes_written) OVERRIDE {
|
| + // TODO(ericu): Check bytes_written against the blob's snapshot value.
|
| + DCHECK(waiting_for_callback_);
|
| + DCHECK(!succeeded || bytes_written >= 0);
|
| + waiting_for_callback_ = false;
|
| + if (delegate_.get()) // Only present for Blob, not File.
|
| + content::BrowserThread::DeleteSoon(
|
| + content::BrowserThread::IO, FROM_HERE, delegate_.release());
|
| + if (aborted_) {
|
| + self_ref_ = NULL;
|
| + return;
|
| + }
|
| + if (succeeded)
|
| + WriteNextFile();
|
| + else
|
| + callback_->Run(false);
|
| + }
|
| +
|
| + virtual void Abort() OVERRIDE {
|
| + if (!waiting_for_callback_)
|
| + return;
|
| + self_ref_ = this;
|
| + aborted_ = true;
|
| + }
|
| +
|
| + private:
|
| + virtual ~ChainedBlobWriterImpl() {}
|
| +
|
| + void WriteNextFile() {
|
| + DCHECK(!waiting_for_callback_);
|
| + DCHECK(!aborted_);
|
| + if (iter_ == blobs_.end()) {
|
| + DCHECK(!self_ref_);
|
| + callback_->Run(true);
|
| + return;
|
| + } else {
|
| + if (!backing_store_->WriteBlobFile(database_id_, *iter_, this)) {
|
| + callback_->Run(false);
|
| + return;
|
| + }
|
| + waiting_for_callback_ = true;
|
| + ++iter_;
|
| + }
|
| + }
|
| +
|
| + bool waiting_for_callback_;
|
| + scoped_refptr<ChainedBlobWriterImpl> self_ref_;
|
| + WriteDescriptorVec blobs_;
|
| + WriteDescriptorVec::const_iterator iter_;
|
| + int64 database_id_;
|
| + IndexedDBBackingStore* backing_store_;
|
| + scoped_refptr<IndexedDBBackingStore::BlobWriteCallback> callback_;
|
| + scoped_ptr<FileWriterDelegate> delegate_;
|
| + bool aborted_;
|
| +};
|
| +
|
| +class LocalWriteClosure : public FileWriterDelegate::DelegateWriteCallback,
|
| + public base::RefCounted<LocalWriteClosure> {
|
| + public:
|
| + LocalWriteClosure(IndexedDBBackingStore::Transaction::ChainedBlobWriter*
|
| + chained_blob_writer_,
|
| + base::TaskRunner* task_runner)
|
| + : chained_blob_writer_(chained_blob_writer_),
|
| + task_runner_(task_runner),
|
| + bytes_written_(-1) {}
|
| +
|
| + void Run(base::File::Error rv,
|
| + int64 bytes,
|
| + FileWriterDelegate::WriteProgressStatus write_status) {
|
| + if (write_status == FileWriterDelegate::SUCCESS_IO_PENDING)
|
| + return; // We don't care about progress events.
|
| + if (rv == base::File::FILE_OK) {
|
| + DCHECK(bytes >= 0);
|
| + DCHECK(write_status == FileWriterDelegate::SUCCESS_COMPLETED);
|
| + bytes_written_ = bytes;
|
| + } else {
|
| + DCHECK(write_status == FileWriterDelegate::ERROR_WRITE_STARTED ||
|
| + write_status == FileWriterDelegate::ERROR_WRITE_NOT_STARTED);
|
| + }
|
| + task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&LocalWriteClosure::callBlobCallbackOnIDBTaskRunner,
|
| + this,
|
| + write_status == FileWriterDelegate::SUCCESS_COMPLETED));
|
| + }
|
| +
|
| + void writeBlobToFileOnIOThread(const FilePath& file_path,
|
| + const GURL& blob_url,
|
| + net::URLRequestContext* request_context) {
|
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
|
| + scoped_ptr<fileapi::FileStreamWriter> writer(
|
| + fileapi::FileStreamWriter::CreateForLocalFile(
|
| + task_runner_, file_path, 0,
|
| + fileapi::FileStreamWriter::CREATE_NEW_FILE));
|
| + scoped_ptr<FileWriterDelegate> delegate(
|
| + new FileWriterDelegate(writer.Pass()));
|
| +
|
| + DCHECK(blob_url.is_valid());
|
| + scoped_ptr<net::URLRequest> blob_request(request_context->CreateRequest(
|
| + blob_url, net::DEFAULT_PRIORITY, delegate.get(), NULL));
|
| +
|
| + delegate->Start(blob_request.Pass(),
|
| + base::Bind(&LocalWriteClosure::Run, this));
|
| + chained_blob_writer_->set_delegate(delegate.Pass());
|
| + }
|
| +
|
| + private:
|
| + virtual ~LocalWriteClosure() {}
|
| + friend class base::RefCounted<LocalWriteClosure>;
|
| +
|
| + void callBlobCallbackOnIDBTaskRunner(bool succeeded) {
|
| + DCHECK(task_runner_->RunsTasksOnCurrentThread());
|
| + chained_blob_writer_->ReportWriteCompletion(succeeded, bytes_written_);
|
| + }
|
| +
|
| + IndexedDBBackingStore::Transaction::ChainedBlobWriter* chained_blob_writer_;
|
| + base::TaskRunner* task_runner_;
|
| + int64 bytes_written_;
|
| +};
|
| +
|
| +bool IndexedDBBackingStore::WriteBlobFile(
|
| + int64 database_id,
|
| + const Transaction::WriteDescriptor& descriptor,
|
| + Transaction::ChainedBlobWriter* chained_blob_writer) {
|
| +
|
| + if (!MakeIDBBlobDirectory(blob_path_, database_id, descriptor.key()))
|
| + return false;
|
| +
|
| + FilePath path = GetBlobFileName(database_id, descriptor.key());
|
| +
|
| + if (descriptor.is_file()) {
|
| + DCHECK(!descriptor.file_path().empty());
|
| + if (!base::CopyFile(descriptor.file_path(), path))
|
| + return false;
|
| +
|
| + base::File::Info info;
|
| + if (base::GetFileInfo(descriptor.file_path(), &info)) {
|
| + // TODO(ericu): Validate the snapshot date here. Expand WriteDescriptor
|
| + // to include snapshot date and file size, and check both.
|
| + if (!base::TouchFile(path, info.last_accessed, info.last_modified)) {
|
| + // TODO(ericu): Complain quietly; timestamp's probably not vital.
|
| + }
|
| + } else {
|
| + // TODO(ericu): Complain quietly; timestamp's probably not vital.
|
| + }
|
| +
|
| + task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&Transaction::ChainedBlobWriter::ReportWriteCompletion,
|
| + chained_blob_writer,
|
| + true,
|
| + info.size));
|
| + } else {
|
| + DCHECK(descriptor.url().is_valid());
|
| + scoped_refptr<LocalWriteClosure> write_closure(
|
| + new LocalWriteClosure(chained_blob_writer, task_runner_));
|
| + content::BrowserThread::PostTask(
|
| + content::BrowserThread::IO,
|
| + FROM_HERE,
|
| + base::Bind(&LocalWriteClosure::writeBlobToFileOnIOThread,
|
| + write_closure.get(),
|
| + path,
|
| + descriptor.url(),
|
| + request_context_));
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +// This assumes a file path of dbId/second-to-LSB-of-counter/counter.
|
| +FilePath IndexedDBBackingStore::GetBlobFileName(int64 database_id, int64 key) {
|
| + return GetBlobFileNameForKey(blob_path_, database_id, key);
|
| +}
|
| +
|
| static bool CheckIndexAndMetaDataKey(const LevelDBIterator* it,
|
| const std::string& stop_key,
|
| int64 index_id,
|
| @@ -1809,6 +2052,16 @@ leveldb::Status IndexedDBBackingStore::GetIndexes(
|
| return s;
|
| }
|
|
|
| +bool IndexedDBBackingStore::RemoveBlobFile(int64 database_id, int64 key) {
|
| + FilePath fileName = GetBlobFileName(database_id, key);
|
| + return base::DeleteFile(fileName, false);
|
| +}
|
| +
|
| +bool IndexedDBBackingStore::RemoveBlobDirectory(int64 database_id) {
|
| + FilePath dirName = GetBlobDirectoryName(blob_path_, database_id);
|
| + return base::DeleteFile(dirName, true);
|
| +}
|
| +
|
| WARN_UNUSED_RESULT static leveldb::Status SetMaxIndexId(
|
| LevelDBTransaction* transaction,
|
| int64 database_id,
|
| @@ -2946,9 +3199,59 @@ leveldb::Status IndexedDBBackingStore::Transaction::Commit() {
|
| return s;
|
| }
|
|
|
| +
|
| +class IndexedDBBackingStore::Transaction::BlobWriteCallbackWrapper
|
| + : public IndexedDBBackingStore::BlobWriteCallback {
|
| + public:
|
| + BlobWriteCallbackWrapper(IndexedDBBackingStore::Transaction* transaction,
|
| + scoped_refptr<BlobWriteCallback> callback)
|
| + : transaction_(transaction), callback_(callback) {}
|
| + virtual void Run(bool succeeded) OVERRIDE {
|
| + callback_->Run(succeeded);
|
| + transaction_->chained_blob_writer_ = NULL;
|
| + }
|
| +
|
| + private:
|
| + virtual ~BlobWriteCallbackWrapper() {}
|
| + friend class base::RefCounted<IndexedDBBackingStore::BlobWriteCallback>;
|
| +
|
| + IndexedDBBackingStore::Transaction* transaction_;
|
| + scoped_refptr<BlobWriteCallback> callback_;
|
| +};
|
| +
|
| +void IndexedDBBackingStore::Transaction::WriteNewBlobs(
|
| + BlobEntryKeyValuePairVec& new_blob_entries,
|
| + WriteDescriptorVec& new_files_to_write,
|
| + scoped_refptr<BlobWriteCallback> callback) {
|
| + DCHECK_GT(new_files_to_write.size(), 0UL);
|
| + DCHECK_GT(database_id_, 0);
|
| + BlobEntryKeyValuePairVec::iterator blob_entry_iter;
|
| + for (blob_entry_iter = new_blob_entries.begin();
|
| + blob_entry_iter != new_blob_entries.end();
|
| + ++blob_entry_iter) {
|
| + // Add the new blob-table entry for each blob to the main transaction, or
|
| + // remove any entry that may exist if there's no new one.
|
| + if (!blob_entry_iter->second.size())
|
| + transaction_->Remove(blob_entry_iter->first.Encode());
|
| + else
|
| + transaction_->Put(blob_entry_iter->first.Encode(),
|
| + &blob_entry_iter->second);
|
| + }
|
| + // Creating the writer will start it going asynchronously.
|
| + chained_blob_writer_ =
|
| + new ChainedBlobWriterImpl(database_id_,
|
| + backing_store_,
|
| + new_files_to_write,
|
| + new BlobWriteCallbackWrapper(this, callback));
|
| +}
|
| +
|
| void IndexedDBBackingStore::Transaction::Rollback() {
|
| IDB_TRACE("IndexedDBBackingStore::Transaction::Rollback");
|
| DCHECK(transaction_.get());
|
| + if (chained_blob_writer_) {
|
| + chained_blob_writer_->Abort();
|
| + chained_blob_writer_ = NULL;
|
| + }
|
| transaction_->Rollback();
|
| transaction_ = NULL;
|
| }
|
| @@ -3004,4 +3307,14 @@ void IndexedDBBackingStore::Transaction::PutBlobInfo(
|
| DCHECK(!handles || !handles->size());
|
| }
|
|
|
| +IndexedDBBackingStore::Transaction::WriteDescriptor::WriteDescriptor(
|
| + const GURL& url,
|
| + int64_t key)
|
| + : is_file_(false), url_(url), key_(key) {}
|
| +
|
| +IndexedDBBackingStore::Transaction::WriteDescriptor::WriteDescriptor(
|
| + const FilePath& file_path,
|
| + int64_t key)
|
| + : is_file_(true), file_path_(file_path), key_(key) {}
|
| +
|
| } // namespace content
|
|
|