Chromium Code Reviews| Index: components/offline_pages/offline_page_metadata_store_impl.cc |
| diff --git a/components/offline_pages/offline_page_metadata_store_impl.cc b/components/offline_pages/offline_page_metadata_store_impl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5d5aafb7b24ab884323eb13cb3aaadc026f75ffb |
| --- /dev/null |
| +++ b/components/offline_pages/offline_page_metadata_store_impl.cc |
| @@ -0,0 +1,314 @@ |
| +// Copyright 2015 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 "components/offline_pages/offline_page_metadata_store_impl.h" |
|
Dmitry Titov
2015/06/04 00:25:53
If we move the dependency on leveldb to embedder,
fgorski
2015/06/05 21:28:42
Done.
|
| + |
| +#include "base/bind.h" |
| +#include "base/files/file_path.h" |
| +#include "base/location.h" |
| +#include "base/sequenced_task_runner.h" |
| +#include "base/thread_task_runner_handle.h" |
| +#include "components/offline_pages/offline_page_item.h" |
| +#include "components/offline_pages/proto/offline_pages.pb.h" |
| +#include "third_party/leveldatabase/env_chromium.h" |
| +#include "third_party/leveldatabase/src/include/leveldb/db.h" |
| +#include "url/gurl.h" |
| + |
| +namespace offline_pages { |
| + |
| +namespace { |
| + |
| +const char kOffinePageKeyStart[] = "offline-page1-"; |
| +const char kOffinePageKeyEnd[] = "offline-page2-"; |
| + |
| +// Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore |
| +// outlive the slice. |
| +// For example: MakeSlice(MakeOutgoingKey(x)) is invalid. |
| +leveldb::Slice MakeSlice(const base::StringPiece& s) { |
| + return leveldb::Slice(s.begin(), s.size()); |
| +} |
| + |
| +std::string MakeKey(const std::string& key) { |
| + return kOffinePageKeyStart + key; |
| +} |
| + |
| +std::string SerializeOfflinePage(const OfflinePageItem& item) { |
| + offline_pages_proto::OfflinePageItem item_proto; |
| + item_proto.set_url(item.url.spec()); |
| + item_proto.set_title(item.title); |
| + item_proto.set_version(item.version); |
| + item_proto.set_file_path(item.file_path.value()); |
| + item_proto.set_file_size(item.file_size); |
| + item_proto.set_creation_time(item.creation_time.ToInternalValue()); |
| + item_proto.set_last_access_time(item.last_access_time.ToInternalValue()); |
| + return item_proto.SerializeAsString(); |
| +} |
| + |
| +bool DeserializeOfflinePage(const std::string& serialized, |
| + OfflinePageItem* item) { |
| + offline_pages_proto::OfflinePageItem item_proto; |
| + if (!item_proto.ParseFromString(serialized)) { |
| + LOG(ERROR) << "Failed to parse serialized offline page metadata."; |
| + return false; |
| + } |
| + if (!item) { |
| + LOG(ERROR) << "Item pointer should be initialized."; |
| + return false; |
| + } |
| + |
| + item->url = GURL(item_proto.url()); |
| + item->title = item_proto.title(); |
| + item->version = item_proto.version(); |
| + item->file_path = base::FilePath(item_proto.file_path()); |
| + if (item_proto.has_file_size()) { |
| + item->file_size = item_proto.file_size(); |
| + } |
| + if (item_proto.has_creation_time()) { |
| + item->creation_time = |
| + base::Time::FromInternalValue(item_proto.creation_time()); |
| + } |
| + if (item_proto.has_last_access_time()) { |
| + item->last_access_time = |
| + base::Time::FromInternalValue(item_proto.last_access_time()); |
| + } |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +class OfflinePageMetadataStoreImpl::Backend |
| + : public base::RefCountedThreadSafe<OfflinePageMetadataStoreImpl::Backend> { |
| + public: |
| + Backend(const base::FilePath& path, |
| + scoped_refptr<base::SequencedTaskRunner> foreground_task_runner); |
| + |
| + void Close(); |
| + |
| + // Blocking implementations methods. |
| + void Load(const LoadCallback& callback); |
| + void AddOfflinePage(const OfflinePageItem& offline_page_item, |
| + const UpdateCallback& callback); |
| + void RemoveOfflinePage(const GURL& page_url, const UpdateCallback& callback); |
| + void Destroy(const UpdateCallback& callback); |
| + |
| + private: |
| + friend class base::RefCountedThreadSafe<Backend>; |
| + ~Backend(); |
| + |
| + Status EnsureStoreIsOpen(); |
| + |
| + bool LoadOfflinePages(std::vector<std::string>* serialized_offline_pages); |
| + |
| + const base::FilePath path_; |
| + scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_; |
| + scoped_ptr<leveldb::DB> db_; |
| +}; |
| + |
| +OfflinePageMetadataStoreImpl::Backend::Backend( |
| + const base::FilePath& path, |
| + scoped_refptr<base::SequencedTaskRunner> foreground_task_runner) |
| + : path_(path), foreground_task_runner_(foreground_task_runner) { |
| +} |
| + |
| +OfflinePageMetadataStoreImpl::Backend::~Backend() { |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Backend::Load(const LoadCallback& callback) { |
| + if (EnsureStoreIsOpen() == FAILED_TO_OPEN_STORE) { |
| + foreground_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(callback, FAILED_TO_OPEN_STORE, |
| + std::vector<OfflinePageItem>())); |
| + return; |
| + } |
| + |
| + std::vector<std::string> serialized_offline_pages; |
| + if (!LoadOfflinePages(&serialized_offline_pages)) { |
| + foreground_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(callback, FAILED_TO_LOAD_STORE, |
| + std::vector<OfflinePageItem>())); |
| + return; |
| + } |
| + |
| + std::vector<OfflinePageItem> result; |
| + for (auto iter = serialized_offline_pages.cbegin(); |
| + iter != serialized_offline_pages.cend(); ++iter) { |
| + OfflinePageItem item; |
| + if (!DeserializeOfflinePage(*iter, &item)) { |
| + foreground_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(callback, FAILED_TO_DESERIALIZE, |
| + std::vector<OfflinePageItem>())); |
| + return; |
| + } |
| + result.push_back(item); |
| + } |
| + |
| + foreground_task_runner_->PostTask(FROM_HERE, |
| + base::Bind(callback, SUCCESS, result)); |
| +} |
| + |
| +OfflinePageMetadataStore::Status |
| +OfflinePageMetadataStoreImpl::Backend::EnsureStoreIsOpen() { |
| + if (!db_.get()) { |
| + leveldb::Options options; |
| + options.create_if_missing = true; |
| + options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; |
| + leveldb::DB* db; |
| + leveldb::Status status = |
| + leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db); |
| + if (!status.ok()) { |
| + LOG(ERROR) << "Failed to open database " << path_.value() << ": " |
| + << status.ToString(); |
| + return FAILED_TO_OPEN_STORE; |
| + } |
| + db_.reset(db); |
| + } |
| + |
| + return SUCCESS; |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Backend::Close() { |
| + DVLOG(1) << "Closing the offline page metadata store."; |
| + db_.reset(); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Backend::AddOfflinePage( |
| + const OfflinePageItem& offline_page_item, |
| + const UpdateCallback& callback) { |
| + DVLOG(1) << "Saving an entry in offline pages store with URL: " |
| + << offline_page_item.url.spec(); |
| + Status status = EnsureStoreIsOpen(); |
| + if (status == FAILED_TO_OPEN_STORE) { |
| + LOG(ERROR) << "OfflinePage store is not open."; |
| + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, status)); |
| + return; |
| + } |
| + |
| + leveldb::WriteOptions write_options; |
| + write_options.sync = true; |
| + |
| + std::string key = MakeKey(offline_page_item.url.spec()); |
| + leveldb::Slice key_slice = MakeSlice(key); |
| + |
| + std::string serialized_value = SerializeOfflinePage(offline_page_item); |
| + leveldb::Slice value_slice = MakeSlice(serialized_value); |
| + |
| + leveldb::Status s = db_->Put(write_options, key_slice, value_slice); |
| + if (!s.ok()) { |
| + LOG(ERROR) << "Offline page store level DB Put failed for: " |
| + << offline_page_item.url.spec() << " Status: " << s.ToString(); |
| + } |
| + |
| + status = s.ok() ? SUCCESS : FAILED_TO_UPDATE_STORE; |
| + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, status)); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Backend::RemoveOfflinePage( |
| + const GURL& page_url, |
| + const UpdateCallback& callback) { |
| + DVLOG(1) << "Deleting an entry in offline pages store with URL: " |
| + << page_url.spec(); |
| + Status status = EnsureStoreIsOpen(); |
| + if (status == FAILED_TO_OPEN_STORE) { |
| + LOG(ERROR) << "OfflinePage store is not open."; |
| + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, status)); |
| + return; |
| + } |
| + |
| + leveldb::WriteOptions write_options; |
| + write_options.sync = true; |
| + |
| + std::string key = MakeKey(page_url.spec()); |
| + leveldb::Slice key_slice = MakeSlice(key); |
| + leveldb::Status s = db_->Delete(write_options, key_slice); |
| + if (!s.ok()) { |
| + LOG(ERROR) << "Offline page store LevelDB Delete failed for: " |
| + << page_url.spec() << " Status: " << s.ToString(); |
| + } |
| + |
| + status = s.ok() ? SUCCESS : FAILED_TO_UPDATE_STORE; |
| + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, status)); |
| +} |
| + |
| +bool OfflinePageMetadataStoreImpl::Backend::LoadOfflinePages( |
| + std::vector<std::string>* serialized_offline_pages) { |
| + leveldb::ReadOptions read_options; |
| + read_options.verify_checksums = true; |
| + |
| + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); |
| + leveldb::Slice from_key = MakeSlice(kOffinePageKeyStart); |
| + for (iter->Seek(from_key); |
| + iter->Valid() && iter->key().ToString() < kOffinePageKeyEnd; |
| + iter->Next()) { |
| + if (iter->value().empty()) { |
| + LOG(ERROR) << "Error reading offline page metadata with key " |
| + << iter->key().ToString(); |
| + return false; |
| + } |
| + DVLOG(1) << "Found incoming message with URL " << iter->key().ToString(); |
| + serialized_offline_pages->push_back(iter->value().ToString()); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Backend::Destroy( |
| + const UpdateCallback& callback) { |
| + DVLOG(1) << "Destroying Offline Page Metadata store."; |
| + db_.reset(); |
| + const leveldb::Status s = |
| + leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options()); |
| + if (!s.ok()) |
| + LOG(ERROR) << "Destroy failed: " << s.ToString(); |
| + |
| + Status status = s.ok() ? SUCCESS : FAILED_TO_DESTROY_STORE; |
| + foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, status)); |
| +} |
| + |
| +OfflinePageMetadataStoreImpl::OfflinePageMetadataStoreImpl( |
| + const base::FilePath& path, |
| + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) |
| + : backend_(new Backend(path, base::ThreadTaskRunnerHandle::Get())), |
| + blocking_task_runner_(blocking_task_runner) { |
| +} |
| + |
| +OfflinePageMetadataStoreImpl::~OfflinePageMetadataStoreImpl() { |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::AddOfflinePage( |
| + const OfflinePageItem& offline_page_item, |
| + const UpdateCallback& callback) { |
| + blocking_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&OfflinePageMetadataStoreImpl::Backend::AddOfflinePage, |
| + backend_, offline_page_item, callback)); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::RemoveOfflinePage( |
| + const GURL& page_url, |
| + const UpdateCallback& callback) { |
| + blocking_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&OfflinePageMetadataStoreImpl::Backend::RemoveOfflinePage, |
| + backend_, page_url, callback)); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Load(const LoadCallback& callback) { |
| + blocking_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(&OfflinePageMetadataStoreImpl::Backend::Load, |
| + backend_, callback)); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Destroy(const UpdateCallback& callback) { |
| + blocking_task_runner_->PostTask( |
| + FROM_HERE, base::Bind(&OfflinePageMetadataStoreImpl::Backend::Destroy, |
| + backend_, callback)); |
| +} |
| + |
| +void OfflinePageMetadataStoreImpl::Close() { |
| + blocking_task_runner_->PostTask( |
| + FROM_HERE, |
| + base::Bind(&OfflinePageMetadataStoreImpl::Backend::Close, backend_)); |
| +} |
| + |
| +} // namespace offline_pages |