Chromium Code Reviews| 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..402afa670a31bc4ec453f3a2c4f924f1a289ec65 100644 |
| --- a/content/browser/indexed_db/indexed_db_backing_store.cc |
| +++ b/content/browser/indexed_db/indexed_db_backing_store.cc |
| @@ -11,6 +11,7 @@ |
| #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 +27,54 @@ |
| #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("%lx", 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("%lx", key)); |
| + return path; |
| +} |
| + |
| +// This assumes a file path of dbId/second-to-LSB-of-counter/counter. |
| +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,197 @@ 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(); |
| + } |
| + |
| + void set_delegate(scoped_ptr<FileWriterDelegate> delegate) { |
| + delegate_.reset(delegate.release()); |
| + } |
| + |
| + void ReportWriteCompletion(bool succeeded, int64 bytes_written) { |
| + // 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); |
| + } |
| + |
| + void Abort() { |
| + if (!waiting_for_callback_) |
| + return; |
| + self_ref_ = this; |
| + aborted_ = true; |
| + } |
| + |
| + private: |
| + 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: |
| + 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. |
|
jsbell
2014/04/28 18:04:20
Nit: This comment could be moved/removed now.
|
| +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 +2045,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 +3192,56 @@ 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) { |
| + callback_->Run(succeeded); |
| + transaction_->chained_blob_writer_ = NULL; |
| + } |
| + |
| + private: |
| + 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 +3297,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 |