Index: sync/internal_api/attachments/on_disk_attachment_store.cc |
diff --git a/sync/internal_api/attachments/on_disk_attachment_store.cc b/sync/internal_api/attachments/on_disk_attachment_store.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2c27e3be186b21ef7310c48ec25d93d69e806f90 |
--- /dev/null |
+++ b/sync/internal_api/attachments/on_disk_attachment_store.cc |
@@ -0,0 +1,174 @@ |
+// Copyright 2014 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 "sync/internal_api/public/attachments/on_disk_attachment_store.h" |
+ |
+#include "base/bind.h" |
+#include "base/callback.h" |
+#include "base/location.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/sequenced_task_runner.h" |
+#include "sync/protocol/attachments.pb.h" |
+#include "third_party/leveldatabase/src/include/leveldb/db.h" |
+#include "third_party/leveldatabase/src/include/leveldb/options.h" |
+#include "third_party/leveldatabase/src/include/leveldb/slice.h" |
+#include "third_party/leveldatabase/src/include/leveldb/status.h" |
+ |
+namespace syncer { |
+ |
+namespace { |
+ |
+// Prefix for records containing attachment data. |
+const char kDataPrefix[] = "data-"; |
+ |
+const base::FilePath::CharType kLeveldbDirectory[] = |
+ FILE_PATH_LITERAL("leveldb"); |
+} // namespace |
+ |
+OnDiskAttachmentStore::OnDiskAttachmentStore( |
+ const scoped_refptr<base::SequencedTaskRunner>& callback_task_runner) |
+ : callback_task_runner_(callback_task_runner) { |
+} |
+ |
+OnDiskAttachmentStore::~OnDiskAttachmentStore() { |
+} |
+ |
+void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, |
+ const ReadCallback& callback) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(db_); |
+ scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); |
+ scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); |
+ |
+ leveldb::ReadOptions read_options; |
+ // Attachment content is typically large and only read once. Don't cache it on |
+ // db level. |
+ read_options.fill_cache = false; |
+ read_options.verify_checksums = true; |
+ |
+ AttachmentIdList::const_iterator iter = ids.begin(); |
+ const AttachmentIdList::const_iterator end = ids.end(); |
+ for (; iter != end; ++iter) { |
+ const std::string key = CreateDataKeyFromAttachmentId(*iter); |
+ std::string data_str; |
+ leveldb::Status status = db_->Get(read_options, key, &data_str); |
+ if (!status.ok()) { |
+ DVLOG(1) << "DB::Get failed: status=" << status.ToString(); |
+ unavailable_attachments->push_back(*iter); |
+ continue; |
+ } |
+ scoped_refptr<base::RefCountedMemory> data = |
+ base::RefCountedString::TakeString(&data_str); |
+ Attachment attachment = Attachment::CreateWithId(*iter, data); |
+ result_map->insert(std::make_pair(*iter, attachment)); |
+ } |
+ |
+ Result result_code = |
+ unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; |
+ callback_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, |
+ result_code, |
+ base::Passed(&result_map), |
+ base::Passed(&unavailable_attachments))); |
+} |
+ |
+void OnDiskAttachmentStore::Write(const AttachmentList& attachments, |
+ const WriteCallback& callback) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(db_); |
+ Result result_code = SUCCESS; |
+ |
+ leveldb::ReadOptions read_options; |
+ read_options.fill_cache = false; |
+ read_options.verify_checksums = true; |
+ |
+ leveldb::WriteOptions write_options; |
+ write_options.sync = true; |
+ |
+ AttachmentList::const_iterator iter = attachments.begin(); |
+ const AttachmentList::const_iterator end = attachments.end(); |
+ for (; iter != end; ++iter) { |
+ const std::string key = CreateDataKeyFromAttachmentId(iter->GetId()); |
+ |
+ std::string data_str; |
+ // TODO(pavely): crbug/424304 This read is expensive. When I add metadata |
+ // records this read will target metadata record instead of payload record. |
+ leveldb::Status status = db_->Get(read_options, key, &data_str); |
+ if (status.ok()) { |
+ // Entry exists, don't overwrite. |
+ continue; |
+ } else if (!status.IsNotFound()) { |
+ // Entry exists but failed to read. |
+ DVLOG(1) << "DB::Get failed: status=" << status.ToString(); |
+ result_code = UNSPECIFIED_ERROR; |
+ continue; |
+ } |
+ DCHECK(status.IsNotFound()); |
+ |
+ scoped_refptr<base::RefCountedMemory> data = iter->GetData(); |
+ leveldb::Slice data_slice(data->front_as<char>(), data->size()); |
+ status = db_->Put(write_options, key, data_slice); |
+ if (!status.ok()) { |
+ // Failed to write. |
+ DVLOG(1) << "DB::Put failed: status=" << status.ToString(); |
+ result_code = UNSPECIFIED_ERROR; |
+ } |
+ } |
+ callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
+} |
+ |
+void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, |
+ const DropCallback& callback) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(db_); |
+ Result result_code = SUCCESS; |
+ leveldb::WriteOptions write_options; |
+ write_options.sync = true; |
+ AttachmentIdList::const_iterator iter = ids.begin(); |
+ const AttachmentIdList::const_iterator end = ids.end(); |
+ for (; iter != end; ++iter) { |
+ const std::string key = CreateDataKeyFromAttachmentId(*iter); |
+ leveldb::Status status = db_->Delete(write_options, key); |
+ if (!status.ok()) { |
+ // DB::Delete doesn't check if record exists, it returns ok just like |
+ // AttachmentStore::Drop should. |
+ DVLOG(1) << "DB::Delete failed: status=" << status.ToString(); |
+ result_code = UNSPECIFIED_ERROR; |
+ } |
+ } |
+ callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
+} |
+ |
+AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( |
+ const base::FilePath& path) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(!db_); |
+ Result result_code = UNSPECIFIED_ERROR; |
+ base::FilePath leveldb_path = path.Append(kLeveldbDirectory); |
+ |
+ leveldb::DB* db; |
+ leveldb::Options options; |
+ options.create_if_missing = true; |
+ // TODO(pavely): crbug/424287 Consider adding info_log, block_cache and |
+ // filter_policy to options. |
+ leveldb::Status status = |
+ leveldb::DB::Open(options, leveldb_path.AsUTF8Unsafe(), &db); |
+ if (!status.ok()) { |
+ DVLOG(1) << "DB::Open failed: status=" << status.ToString() |
+ << ", path=" << path.AsUTF8Unsafe(); |
+ } else { |
+ db_.reset(db); |
+ result_code = SUCCESS; |
+ } |
+ return result_code; |
+} |
+ |
+std::string OnDiskAttachmentStore::CreateDataKeyFromAttachmentId( |
+ const AttachmentId& attachment_id) { |
+ std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); |
+ return key; |
+} |
+ |
+} // namespace syncer |