Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sync/internal_api/public/attachments/on_disk_attachment_store.h" | 5 #include "sync/internal_api/public/attachments/on_disk_attachment_store.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback.h" | 8 #include "base/callback.h" |
| 9 #include "base/location.h" | 9 #include "base/location.h" |
| 10 #include "base/memory/scoped_ptr.h" | 10 #include "base/memory/scoped_ptr.h" |
| 11 #include "base/sequenced_task_runner.h" | 11 #include "base/sequenced_task_runner.h" |
| 12 #include "sync/internal_api/attachments/proto/attachment_store.pb.h" | 12 #include "sync/internal_api/attachments/proto/attachment_store.pb.h" |
| 13 #include "sync/protocol/attachments.pb.h" | 13 #include "sync/protocol/attachments.pb.h" |
| 14 #include "third_party/leveldatabase/src/include/leveldb/db.h" | 14 #include "third_party/leveldatabase/src/include/leveldb/db.h" |
| 15 #include "third_party/leveldatabase/src/include/leveldb/options.h" | 15 #include "third_party/leveldatabase/src/include/leveldb/options.h" |
| 16 #include "third_party/leveldatabase/src/include/leveldb/slice.h" | 16 #include "third_party/leveldatabase/src/include/leveldb/slice.h" |
| 17 #include "third_party/leveldatabase/src/include/leveldb/status.h" | 17 #include "third_party/leveldatabase/src/include/leveldb/status.h" |
| 18 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" | |
| 18 | 19 |
| 19 namespace syncer { | 20 namespace syncer { |
| 20 | 21 |
| 21 namespace { | 22 namespace { |
| 22 | 23 |
| 23 // Prefix for records containing attachment data. | 24 // Prefix for records containing attachment data. |
| 24 const char kDataPrefix[] = "data-"; | 25 const char kDataPrefix[] = "data-"; |
| 25 | 26 |
| 27 // Prefix for records containing attachment metadata. | |
| 28 const char kMetadataPrefix[] = "metadata-"; | |
|
stanisc
2014/10/30 18:56:59
It probably doesn't matter in this case but I'd me
pavely
2014/10/30 19:54:15
One scenario that we will likely need to do is sca
| |
| 29 | |
| 26 const char kDatabaseMetadataKey[] = "database-metadata"; | 30 const char kDatabaseMetadataKey[] = "database-metadata"; |
| 27 | 31 |
| 28 const int32 kCurrentSchemaVersion = 1; | 32 const int32 kCurrentSchemaVersion = 1; |
| 29 | 33 |
| 30 const base::FilePath::CharType kLeveldbDirectory[] = | 34 const base::FilePath::CharType kLeveldbDirectory[] = |
| 31 FILE_PATH_LITERAL("leveldb"); | 35 FILE_PATH_LITERAL("leveldb"); |
| 32 | 36 |
| 33 leveldb::WriteOptions MakeWriteOptions() { | 37 leveldb::WriteOptions MakeWriteOptions() { |
| 34 leveldb::WriteOptions write_options; | 38 leveldb::WriteOptions write_options; |
| 35 write_options.sync = true; | 39 write_options.sync = true; |
| 36 return write_options; | 40 return write_options; |
| 37 } | 41 } |
| 38 | 42 |
| 43 leveldb::ReadOptions MakeDataReadOptions() { | |
| 44 leveldb::ReadOptions read_options; | |
| 45 // Attachment content is typically large and only read once. Don't cache it on | |
| 46 // db level. | |
| 47 read_options.fill_cache = false; | |
| 48 read_options.verify_checksums = true; | |
| 49 return read_options; | |
| 50 } | |
| 51 | |
| 52 leveldb::ReadOptions MakeMetadataReadOptions() { | |
| 53 leveldb::ReadOptions read_options; | |
| 54 read_options.fill_cache = true; | |
| 55 read_options.verify_checksums = true; | |
| 56 return read_options; | |
| 57 } | |
| 58 | |
| 39 leveldb::Status ReadStoreMetadata( | 59 leveldb::Status ReadStoreMetadata( |
| 40 leveldb::DB* db, | 60 leveldb::DB* db, |
| 41 attachment_store_pb::AttachmentStoreMetadata* metadata) { | 61 attachment_store_pb::StoreMetadata* metadata) { |
| 42 std::string data_str; | 62 std::string data_str; |
| 43 leveldb::ReadOptions read_options; | |
| 44 read_options.fill_cache = false; | |
| 45 read_options.verify_checksums = true; | |
| 46 | 63 |
| 47 leveldb::Status status = | 64 leveldb::Status status = |
| 48 db->Get(read_options, kDatabaseMetadataKey, &data_str); | 65 db->Get(MakeMetadataReadOptions(), kDatabaseMetadataKey, &data_str); |
| 49 if (!status.ok()) | 66 if (!status.ok()) |
| 50 return status; | 67 return status; |
| 51 if (!metadata->ParseFromString(data_str)) | 68 if (!metadata->ParseFromString(data_str)) |
| 52 return leveldb::Status::Corruption("Metadata record corruption"); | 69 return leveldb::Status::Corruption("Metadata record corruption"); |
| 53 return leveldb::Status::OK(); | 70 return leveldb::Status::OK(); |
| 54 } | 71 } |
| 55 | 72 |
| 56 leveldb::Status WriteStoreMetadata( | 73 leveldb::Status WriteStoreMetadata( |
| 57 leveldb::DB* db, | 74 leveldb::DB* db, |
| 58 const attachment_store_pb::AttachmentStoreMetadata& metadata) { | 75 const attachment_store_pb::StoreMetadata& metadata) { |
| 59 std::string data_str; | 76 std::string data_str; |
| 60 | 77 |
| 61 metadata.SerializeToString(&data_str); | 78 metadata.SerializeToString(&data_str); |
| 62 return db->Put(MakeWriteOptions(), kDatabaseMetadataKey, data_str); | 79 return db->Put(MakeWriteOptions(), kDatabaseMetadataKey, data_str); |
| 63 } | 80 } |
| 64 | 81 |
| 65 } // namespace | 82 } // namespace |
| 66 | 83 |
| 67 OnDiskAttachmentStore::OnDiskAttachmentStore( | 84 OnDiskAttachmentStore::OnDiskAttachmentStore( |
| 68 const scoped_refptr<base::SequencedTaskRunner>& callback_task_runner) | 85 const scoped_refptr<base::SequencedTaskRunner>& callback_task_runner) |
| 69 : callback_task_runner_(callback_task_runner) { | 86 : callback_task_runner_(callback_task_runner) { |
| 70 } | 87 } |
| 71 | 88 |
| 72 OnDiskAttachmentStore::~OnDiskAttachmentStore() { | 89 OnDiskAttachmentStore::~OnDiskAttachmentStore() { |
| 73 } | 90 } |
| 74 | 91 |
| 75 void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, | 92 void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, |
| 76 const ReadCallback& callback) { | 93 const ReadCallback& callback) { |
| 77 DCHECK(CalledOnValidThread()); | 94 DCHECK(CalledOnValidThread()); |
| 78 DCHECK(db_); | 95 DCHECK(db_); |
| 79 scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); | 96 scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); |
| 80 scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); | 97 scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); |
| 81 | 98 |
| 82 leveldb::ReadOptions read_options; | |
| 83 // Attachment content is typically large and only read once. Don't cache it on | |
| 84 // db level. | |
| 85 read_options.fill_cache = false; | |
| 86 read_options.verify_checksums = true; | |
| 87 | |
| 88 AttachmentIdList::const_iterator iter = ids.begin(); | 99 AttachmentIdList::const_iterator iter = ids.begin(); |
| 89 const AttachmentIdList::const_iterator end = ids.end(); | 100 const AttachmentIdList::const_iterator end = ids.end(); |
| 90 for (; iter != end; ++iter) { | 101 for (; iter != end; ++iter) { |
| 91 const std::string key = MakeDataKeyFromAttachmentId(*iter); | 102 scoped_ptr<Attachment> attachment = ReadSingleAttachment(*iter); |
| 92 std::string data_str; | 103 if (attachment) { |
| 93 leveldb::Status status = db_->Get(read_options, key, &data_str); | 104 result_map->insert(std::make_pair(*iter, *attachment)); |
| 94 if (!status.ok()) { | 105 } else { |
| 95 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
| 96 unavailable_attachments->push_back(*iter); | 106 unavailable_attachments->push_back(*iter); |
| 97 continue; | |
| 98 } | 107 } |
| 99 scoped_refptr<base::RefCountedMemory> data = | |
| 100 base::RefCountedString::TakeString(&data_str); | |
| 101 Attachment attachment = Attachment::CreateWithId(*iter, data); | |
| 102 result_map->insert(std::make_pair(*iter, attachment)); | |
| 103 } | 108 } |
| 104 | 109 |
| 105 Result result_code = | 110 Result result_code = |
| 106 unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; | 111 unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; |
| 107 callback_task_runner_->PostTask( | 112 callback_task_runner_->PostTask( |
| 108 FROM_HERE, | 113 FROM_HERE, |
| 109 base::Bind(callback, | 114 base::Bind(callback, |
| 110 result_code, | 115 result_code, |
| 111 base::Passed(&result_map), | 116 base::Passed(&result_map), |
| 112 base::Passed(&unavailable_attachments))); | 117 base::Passed(&unavailable_attachments))); |
| 113 } | 118 } |
| 114 | 119 |
| 115 void OnDiskAttachmentStore::Write(const AttachmentList& attachments, | 120 void OnDiskAttachmentStore::Write(const AttachmentList& attachments, |
| 116 const WriteCallback& callback) { | 121 const WriteCallback& callback) { |
| 117 DCHECK(CalledOnValidThread()); | 122 DCHECK(CalledOnValidThread()); |
| 118 DCHECK(db_); | 123 DCHECK(db_); |
| 119 Result result_code = SUCCESS; | 124 Result result_code = SUCCESS; |
| 120 | 125 |
| 121 leveldb::ReadOptions read_options; | |
| 122 read_options.fill_cache = false; | |
| 123 read_options.verify_checksums = true; | |
| 124 | |
| 125 leveldb::WriteOptions write_options = MakeWriteOptions(); | |
| 126 | |
| 127 AttachmentList::const_iterator iter = attachments.begin(); | 126 AttachmentList::const_iterator iter = attachments.begin(); |
| 128 const AttachmentList::const_iterator end = attachments.end(); | 127 const AttachmentList::const_iterator end = attachments.end(); |
| 129 for (; iter != end; ++iter) { | 128 for (; iter != end; ++iter) { |
| 130 const std::string key = MakeDataKeyFromAttachmentId(iter->GetId()); | 129 if (!WriteSingleAttachment(*iter)) |
| 131 | |
| 132 std::string data_str; | |
| 133 // TODO(pavely): crbug/424304 This read is expensive. When I add metadata | |
| 134 // records this read will target metadata record instead of payload record. | |
| 135 leveldb::Status status = db_->Get(read_options, key, &data_str); | |
| 136 if (status.ok()) { | |
| 137 // Entry exists, don't overwrite. | |
| 138 continue; | |
| 139 } else if (!status.IsNotFound()) { | |
| 140 // Entry exists but failed to read. | |
| 141 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
| 142 result_code = UNSPECIFIED_ERROR; | 130 result_code = UNSPECIFIED_ERROR; |
| 143 continue; | |
| 144 } | |
| 145 DCHECK(status.IsNotFound()); | |
| 146 | |
| 147 scoped_refptr<base::RefCountedMemory> data = iter->GetData(); | |
| 148 leveldb::Slice data_slice(data->front_as<char>(), data->size()); | |
| 149 status = db_->Put(write_options, key, data_slice); | |
| 150 if (!status.ok()) { | |
| 151 // Failed to write. | |
| 152 DVLOG(1) << "DB::Put failed: status=" << status.ToString(); | |
| 153 result_code = UNSPECIFIED_ERROR; | |
| 154 } | |
| 155 } | 131 } |
| 156 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); | 132 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
| 157 } | 133 } |
| 158 | 134 |
| 159 void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, | 135 void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, |
| 160 const DropCallback& callback) { | 136 const DropCallback& callback) { |
| 161 DCHECK(CalledOnValidThread()); | 137 DCHECK(CalledOnValidThread()); |
| 162 DCHECK(db_); | 138 DCHECK(db_); |
| 163 Result result_code = SUCCESS; | 139 Result result_code = SUCCESS; |
| 164 leveldb::WriteOptions write_options = MakeWriteOptions(); | 140 leveldb::WriteOptions write_options = MakeWriteOptions(); |
| 165 AttachmentIdList::const_iterator iter = ids.begin(); | 141 AttachmentIdList::const_iterator iter = ids.begin(); |
| 166 const AttachmentIdList::const_iterator end = ids.end(); | 142 const AttachmentIdList::const_iterator end = ids.end(); |
| 167 for (; iter != end; ++iter) { | 143 for (; iter != end; ++iter) { |
| 168 const std::string key = MakeDataKeyFromAttachmentId(*iter); | 144 leveldb::WriteBatch write_batch; |
| 169 leveldb::Status status = db_->Delete(write_options, key); | 145 write_batch.Delete(MakeDataKeyFromAttachmentId(*iter)); |
| 146 write_batch.Delete(MakeMetadataKeyFromAttachmentId(*iter)); | |
| 147 | |
| 148 leveldb::Status status = db_->Write(write_options, &write_batch); | |
| 170 if (!status.ok()) { | 149 if (!status.ok()) { |
| 171 // DB::Delete doesn't check if record exists, it returns ok just like | 150 // DB::Delete doesn't check if record exists, it returns ok just like |
| 172 // AttachmentStore::Drop should. | 151 // AttachmentStore::Drop should. |
| 173 DVLOG(1) << "DB::Delete failed: status=" << status.ToString(); | 152 DVLOG(1) << "DB::Write failed: status=" << status.ToString(); |
| 174 result_code = UNSPECIFIED_ERROR; | 153 result_code = UNSPECIFIED_ERROR; |
| 175 } | 154 } |
| 176 } | 155 } |
| 177 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); | 156 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
| 178 } | 157 } |
| 179 | 158 |
| 180 AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( | 159 AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( |
| 181 const base::FilePath& path) { | 160 const base::FilePath& path) { |
| 182 DCHECK(CalledOnValidThread()); | 161 DCHECK(CalledOnValidThread()); |
| 183 DCHECK(!db_); | 162 DCHECK(!db_); |
| 184 base::FilePath leveldb_path = path.Append(kLeveldbDirectory); | 163 base::FilePath leveldb_path = path.Append(kLeveldbDirectory); |
| 185 | 164 |
| 186 leveldb::DB* db_raw; | 165 leveldb::DB* db_raw; |
| 187 scoped_ptr<leveldb::DB> db; | 166 scoped_ptr<leveldb::DB> db; |
| 188 leveldb::Options options; | 167 leveldb::Options options; |
| 189 options.create_if_missing = true; | 168 options.create_if_missing = true; |
| 190 // TODO(pavely): crbug/424287 Consider adding info_log, block_cache and | 169 // TODO(pavely): crbug/424287 Consider adding info_log, block_cache and |
| 191 // filter_policy to options. | 170 // filter_policy to options. |
| 192 leveldb::Status status = | 171 leveldb::Status status = |
| 193 leveldb::DB::Open(options, leveldb_path.AsUTF8Unsafe(), &db_raw); | 172 leveldb::DB::Open(options, leveldb_path.AsUTF8Unsafe(), &db_raw); |
| 194 if (!status.ok()) { | 173 if (!status.ok()) { |
| 195 DVLOG(1) << "DB::Open failed: status=" << status.ToString() | 174 DVLOG(1) << "DB::Open failed: status=" << status.ToString() |
| 196 << ", path=" << path.AsUTF8Unsafe(); | 175 << ", path=" << path.AsUTF8Unsafe(); |
| 197 return UNSPECIFIED_ERROR; | 176 return UNSPECIFIED_ERROR; |
| 198 } | 177 } |
| 199 | 178 |
| 200 db.reset(db_raw); | 179 db.reset(db_raw); |
| 201 | 180 |
| 202 attachment_store_pb::AttachmentStoreMetadata metadata; | 181 attachment_store_pb::StoreMetadata metadata; |
| 203 status = ReadStoreMetadata(db.get(), &metadata); | 182 status = ReadStoreMetadata(db.get(), &metadata); |
| 204 if (!status.ok() && !status.IsNotFound()) { | 183 if (!status.ok() && !status.IsNotFound()) { |
| 205 DVLOG(1) << "ReadStoreMetadata failed: status=" << status.ToString(); | 184 DVLOG(1) << "ReadStoreMetadata failed: status=" << status.ToString(); |
| 206 return UNSPECIFIED_ERROR; | 185 return UNSPECIFIED_ERROR; |
| 207 } | 186 } |
| 208 if (status.IsNotFound()) { | 187 if (status.IsNotFound()) { |
| 209 // Brand new database. | 188 // Brand new database. |
| 210 metadata.set_schema_version(kCurrentSchemaVersion); | 189 metadata.set_schema_version(kCurrentSchemaVersion); |
| 211 status = WriteStoreMetadata(db.get(), metadata); | 190 status = WriteStoreMetadata(db.get(), metadata); |
| 212 if (!status.ok()) { | 191 if (!status.ok()) { |
| 213 DVLOG(1) << "WriteStoreMetadata failed: status=" << status.ToString(); | 192 DVLOG(1) << "WriteStoreMetadata failed: status=" << status.ToString(); |
| 214 return UNSPECIFIED_ERROR; | 193 return UNSPECIFIED_ERROR; |
| 215 } | 194 } |
| 216 } | 195 } |
| 217 DCHECK(status.ok()); | 196 DCHECK(status.ok()); |
| 218 | 197 |
| 219 // Upgrade code goes here. | 198 // Upgrade code goes here. |
| 220 | 199 |
| 221 if (metadata.schema_version() != kCurrentSchemaVersion) { | 200 if (metadata.schema_version() != kCurrentSchemaVersion) { |
| 222 DVLOG(1) << "Unknown schema version: " << metadata.schema_version(); | 201 DVLOG(1) << "Unknown schema version: " << metadata.schema_version(); |
| 223 return UNSPECIFIED_ERROR; | 202 return UNSPECIFIED_ERROR; |
| 224 } | 203 } |
| 225 | 204 |
| 226 db_ = db.Pass(); | 205 db_ = db.Pass(); |
| 227 return SUCCESS; | 206 return SUCCESS; |
| 228 } | 207 } |
| 229 | 208 |
| 209 scoped_ptr<Attachment> OnDiskAttachmentStore::ReadSingleAttachment( | |
| 210 const AttachmentId& attachment_id) { | |
| 211 scoped_ptr<Attachment> attachment; | |
| 212 | |
| 213 const std::string key = MakeDataKeyFromAttachmentId(attachment_id); | |
| 214 std::string data_str; | |
| 215 leveldb::Status status = db_->Get(MakeDataReadOptions(), key, &data_str); | |
| 216 if (status.ok()) { | |
| 217 scoped_refptr<base::RefCountedMemory> data = | |
| 218 base::RefCountedString::TakeString(&data_str); | |
| 219 attachment.reset( | |
| 220 new Attachment(Attachment::CreateWithId(attachment_id, data))); | |
| 221 } else { | |
| 222 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
| 223 } | |
| 224 return attachment.Pass(); | |
| 225 } | |
| 226 | |
| 227 bool OnDiskAttachmentStore::WriteSingleAttachment( | |
| 228 const Attachment& attachment) { | |
| 229 const std::string metadata_key = | |
| 230 MakeMetadataKeyFromAttachmentId(attachment.GetId()); | |
| 231 const std::string data_key = MakeDataKeyFromAttachmentId(attachment.GetId()); | |
| 232 | |
| 233 std::string metadata_str; | |
| 234 leveldb::Status status = | |
| 235 db_->Get(MakeMetadataReadOptions(), metadata_key, &metadata_str); | |
| 236 if (status.ok()) { | |
| 237 // Entry exists, don't overwrite. | |
| 238 return true; | |
| 239 } else if (!status.IsNotFound()) { | |
| 240 // Entry exists but failed to read. | |
| 241 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
| 242 return false; | |
| 243 } | |
| 244 DCHECK(status.IsNotFound()); | |
| 245 | |
| 246 leveldb::WriteBatch write_batch; | |
| 247 // Write metadata. | |
| 248 attachment_store_pb::RecordMetadata metadata; | |
| 249 metadata.set_attachment_size(attachment.GetData()->size()); | |
| 250 metadata_str = metadata.SerializeAsString(); | |
| 251 write_batch.Put(metadata_key, metadata_str); | |
| 252 // Write data. | |
| 253 scoped_refptr<base::RefCountedMemory> data = attachment.GetData(); | |
| 254 leveldb::Slice data_slice(data->front_as<char>(), data->size()); | |
| 255 write_batch.Put(data_key, data_slice); | |
| 256 | |
| 257 status = db_->Write(MakeWriteOptions(), &write_batch); | |
| 258 if (!status.ok()) { | |
| 259 // Failed to write. | |
| 260 DVLOG(1) << "DB::Write failed: status=" << status.ToString(); | |
| 261 return false; | |
| 262 } | |
| 263 return true; | |
| 264 } | |
| 265 | |
| 230 std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId( | 266 std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId( |
| 231 const AttachmentId& attachment_id) { | 267 const AttachmentId& attachment_id) { |
| 232 std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); | 268 std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); |
| 233 return key; | 269 return key; |
| 234 } | 270 } |
| 235 | 271 |
| 272 std::string OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId( | |
| 273 const AttachmentId& attachment_id) { | |
| 274 std::string key = kMetadataPrefix + attachment_id.GetProto().unique_id(); | |
| 275 return key; | |
| 276 } | |
| 277 | |
| 236 } // namespace syncer | 278 } // namespace syncer |
| OLD | NEW |