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
3 // found in the LICENSE file.
5 #include "sync/internal_api/public/attachments/on_disk_attachment_store.h"
8 #include "base/callback.h"
9 #include "base/location.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/metrics/histogram.h"
12 #include "base/sequenced_task_runner.h"
13 #include "sync/internal_api/attachments/proto/attachment_store.pb.h"
14 #include "sync/internal_api/public/attachments/attachment_util.h"
15 #include "sync/protocol/attachments.pb.h"
16 #include "third_party/leveldatabase/env_chromium.h"
17 #include "third_party/leveldatabase/src/include/leveldb/db.h"
18 #include "third_party/leveldatabase/src/include/leveldb/options.h"
19 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
20 #include "third_party/leveldatabase/src/include/leveldb/status.h"
21 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
27 // Prefix for records containing attachment data.
28 const char kDataPrefix
[] = "data-";
30 // Prefix for records containing attachment metadata.
31 const char kMetadataPrefix
[] = "metadata-";
33 const char kDatabaseMetadataKey
[] = "database-metadata";
35 const int32 kCurrentSchemaVersion
= 1;
37 const base::FilePath::CharType kLeveldbDirectory
[] =
38 FILE_PATH_LITERAL("leveldb");
40 // Converts syncer::AttachmentStore::Component values into
41 // attachment_store_pb::RecordMetadata::Component.
42 attachment_store_pb::RecordMetadata::Component
ComponentToProto(
43 syncer::AttachmentStore::Component component
) {
45 case AttachmentStore::MODEL_TYPE
:
46 return attachment_store_pb::RecordMetadata::MODEL_TYPE
;
47 case AttachmentStore::SYNC
:
48 return attachment_store_pb::RecordMetadata::SYNC
;
51 return attachment_store_pb::RecordMetadata::UNKNOWN
;
54 leveldb::WriteOptions
MakeWriteOptions() {
55 leveldb::WriteOptions write_options
;
56 write_options
.sync
= true;
60 leveldb::ReadOptions
MakeNonCachingReadOptions() {
61 leveldb::ReadOptions read_options
;
62 read_options
.fill_cache
= false;
63 read_options
.verify_checksums
= true;
67 leveldb::ReadOptions
MakeCachingReadOptions() {
68 leveldb::ReadOptions read_options
;
69 read_options
.fill_cache
= true;
70 read_options
.verify_checksums
= true;
74 leveldb::Status
ReadStoreMetadata(
76 attachment_store_pb::StoreMetadata
* metadata
) {
79 leveldb::Status status
=
80 db
->Get(MakeCachingReadOptions(), kDatabaseMetadataKey
, &data_str
);
83 if (!metadata
->ParseFromString(data_str
))
84 return leveldb::Status::Corruption("Metadata record corruption");
85 return leveldb::Status::OK();
88 leveldb::Status
WriteStoreMetadata(
90 const attachment_store_pb::StoreMetadata
& metadata
) {
93 metadata
.SerializeToString(&data_str
);
94 return db
->Put(MakeWriteOptions(), kDatabaseMetadataKey
, data_str
);
97 // Adds reference to component into RecordMetadata::component set.
98 // Returns true if record_metadata was modified and needs to be written to disk.
99 bool SetReferenceInRecordMetadata(
100 attachment_store_pb::RecordMetadata
* record_metadata
,
101 attachment_store_pb::RecordMetadata::Component proto_component
) {
102 DCHECK(record_metadata
);
103 for (const int component
: record_metadata
->component()) {
104 if (component
== proto_component
)
107 record_metadata
->add_component(proto_component
);
111 // Drops reference to component from RecordMetadata::component set.
112 // Returns true if record_metadata was modified and needs to be written to disk.
113 bool DropReferenceInRecordMetadata(
114 attachment_store_pb::RecordMetadata
* record_metadata
,
115 attachment_store_pb::RecordMetadata::Component proto_component
) {
116 DCHECK(record_metadata
);
117 bool component_removed
= false;
118 ::google::protobuf::RepeatedField
<int>* mutable_components
=
119 record_metadata
->mutable_component();
120 for (int i
= 0; i
< mutable_components
->size();) {
121 if (mutable_components
->Get(i
) == proto_component
) {
122 if (i
< mutable_components
->size() - 1) {
123 // Don't swap last element with itself.
124 mutable_components
->SwapElements(i
, mutable_components
->size() - 1);
126 mutable_components
->RemoveLast();
127 component_removed
= true;
132 return component_removed
;
137 OnDiskAttachmentStore::OnDiskAttachmentStore(
138 const scoped_refptr
<base::SequencedTaskRunner
>& callback_task_runner
,
139 const base::FilePath
& path
)
140 : AttachmentStoreBackend(callback_task_runner
), path_(path
) {
143 OnDiskAttachmentStore::~OnDiskAttachmentStore() {
146 void OnDiskAttachmentStore::Init(
147 const AttachmentStore::InitCallback
& callback
) {
148 DCHECK(CalledOnValidThread());
149 AttachmentStore::Result result_code
= OpenOrCreate(path_
);
150 UMA_HISTOGRAM_ENUMERATION("Sync.Attachments.StoreInitResult", result_code
,
151 AttachmentStore::RESULT_SIZE
);
152 PostCallback(base::Bind(callback
, result_code
));
155 void OnDiskAttachmentStore::Read(
156 const AttachmentIdList
& ids
,
157 const AttachmentStore::ReadCallback
& callback
) {
158 DCHECK(CalledOnValidThread());
159 scoped_ptr
<AttachmentMap
> result_map(new AttachmentMap());
160 scoped_ptr
<AttachmentIdList
> unavailable_attachments(new AttachmentIdList());
162 AttachmentStore::Result result_code
=
163 AttachmentStore::STORE_INITIALIZATION_FAILED
;
166 result_code
= AttachmentStore::SUCCESS
;
167 AttachmentIdList::const_iterator iter
= ids
.begin();
168 const AttachmentIdList::const_iterator end
= ids
.end();
169 for (; iter
!= end
; ++iter
) {
170 scoped_ptr
<Attachment
> attachment
;
171 attachment
= ReadSingleAttachment(*iter
);
173 result_map
->insert(std::make_pair(*iter
, *attachment
));
175 unavailable_attachments
->push_back(*iter
);
178 result_code
= unavailable_attachments
->empty()
179 ? AttachmentStore::SUCCESS
180 : AttachmentStore::UNSPECIFIED_ERROR
;
182 *unavailable_attachments
= ids
;
185 PostCallback(base::Bind(callback
, result_code
, base::Passed(&result_map
),
186 base::Passed(&unavailable_attachments
)));
189 void OnDiskAttachmentStore::Write(
190 AttachmentStore::Component component
,
191 const AttachmentList
& attachments
,
192 const AttachmentStore::WriteCallback
& callback
) {
193 DCHECK(CalledOnValidThread());
194 AttachmentStore::Result result_code
=
195 AttachmentStore::STORE_INITIALIZATION_FAILED
;
198 result_code
= AttachmentStore::SUCCESS
;
199 AttachmentList::const_iterator iter
= attachments
.begin();
200 const AttachmentList::const_iterator end
= attachments
.end();
201 for (; iter
!= end
; ++iter
) {
202 if (!WriteSingleAttachment(*iter
, component
))
203 result_code
= AttachmentStore::UNSPECIFIED_ERROR
;
206 PostCallback(base::Bind(callback
, result_code
));
209 void OnDiskAttachmentStore::SetReference(AttachmentStore::Component component
,
210 const AttachmentIdList
& ids
) {
211 DCHECK(CalledOnValidThread());
214 attachment_store_pb::RecordMetadata::Component proto_component
=
215 ComponentToProto(component
);
216 for (const auto& id
: ids
) {
217 attachment_store_pb::RecordMetadata record_metadata
;
218 if (!ReadSingleRecordMetadata(id
, &record_metadata
))
220 if (SetReferenceInRecordMetadata(&record_metadata
, proto_component
))
221 WriteSingleRecordMetadata(id
, record_metadata
);
225 void OnDiskAttachmentStore::DropReference(
226 AttachmentStore::Component component
,
227 const AttachmentIdList
& ids
,
228 const AttachmentStore::DropCallback
& callback
) {
229 DCHECK(CalledOnValidThread());
230 AttachmentStore::Result result_code
=
231 AttachmentStore::STORE_INITIALIZATION_FAILED
;
233 attachment_store_pb::RecordMetadata::Component proto_component
=
234 ComponentToProto(component
);
235 result_code
= AttachmentStore::SUCCESS
;
236 leveldb::WriteOptions write_options
= MakeWriteOptions();
237 for (const auto& id
: ids
) {
238 attachment_store_pb::RecordMetadata record_metadata
;
239 if (!ReadSingleRecordMetadata(id
, &record_metadata
))
240 continue; // Record not found.
241 if (!DropReferenceInRecordMetadata(&record_metadata
, proto_component
))
242 continue; // Component is not in components set. Metadata was not
244 if (record_metadata
.component_size() == 0) {
245 // Last reference dropped. Need to delete attachment.
246 leveldb::WriteBatch write_batch
;
247 write_batch
.Delete(MakeDataKeyFromAttachmentId(id
));
248 write_batch
.Delete(MakeMetadataKeyFromAttachmentId(id
));
250 leveldb::Status status
= db_
->Write(write_options
, &write_batch
);
252 // DB::Delete doesn't check if record exists, it returns ok just like
253 // AttachmentStore::Drop should.
254 DVLOG(1) << "DB::Write failed: status=" << status
.ToString();
255 result_code
= AttachmentStore::UNSPECIFIED_ERROR
;
258 WriteSingleRecordMetadata(id
, record_metadata
);
262 PostCallback(base::Bind(callback
, result_code
));
265 void OnDiskAttachmentStore::ReadMetadata(
266 const AttachmentIdList
& ids
,
267 const AttachmentStore::ReadMetadataCallback
& callback
) {
268 DCHECK(CalledOnValidThread());
269 AttachmentStore::Result result_code
=
270 AttachmentStore::STORE_INITIALIZATION_FAILED
;
271 scoped_ptr
<AttachmentMetadataList
> metadata_list(
272 new AttachmentMetadataList());
274 result_code
= AttachmentStore::SUCCESS
;
275 // TODO(pavely): ReadMetadata should only return attachments with component
276 // reference similarly to ReadAllMetadata behavior.
277 for (const auto& id
: ids
) {
278 attachment_store_pb::RecordMetadata record_metadata
;
279 if (ReadSingleRecordMetadata(id
, &record_metadata
)) {
280 metadata_list
->push_back(MakeAttachmentMetadata(id
, record_metadata
));
282 result_code
= AttachmentStore::UNSPECIFIED_ERROR
;
286 PostCallback(base::Bind(callback
, result_code
, base::Passed(&metadata_list
)));
289 void OnDiskAttachmentStore::ReadAllMetadata(
290 AttachmentStore::Component component
,
291 const AttachmentStore::ReadMetadataCallback
& callback
) {
292 DCHECK(CalledOnValidThread());
293 AttachmentStore::Result result_code
=
294 AttachmentStore::STORE_INITIALIZATION_FAILED
;
295 scoped_ptr
<AttachmentMetadataList
> metadata_list(
296 new AttachmentMetadataList());
299 attachment_store_pb::RecordMetadata::Component proto_component
=
300 ComponentToProto(component
);
301 result_code
= AttachmentStore::SUCCESS
;
302 scoped_ptr
<leveldb::Iterator
> db_iterator(
303 db_
->NewIterator(MakeNonCachingReadOptions()));
305 for (db_iterator
->Seek(kMetadataPrefix
); db_iterator
->Valid();
306 db_iterator
->Next()) {
307 leveldb::Slice key
= db_iterator
->key();
308 if (!key
.starts_with(kMetadataPrefix
)) {
311 // Make AttachmentId from levelDB key.
312 key
.remove_prefix(strlen(kMetadataPrefix
));
313 sync_pb::AttachmentIdProto id_proto
;
314 id_proto
.set_unique_id(key
.ToString());
315 AttachmentId id
= AttachmentId::CreateFromProto(id_proto
);
316 // Parse metadata record.
317 attachment_store_pb::RecordMetadata record_metadata
;
318 if (!record_metadata
.ParseFromString(db_iterator
->value().ToString())) {
319 DVLOG(1) << "RecordMetadata::ParseFromString failed";
320 result_code
= AttachmentStore::UNSPECIFIED_ERROR
;
323 // Check if attachment has reference from component.
324 bool has_reference_from_component
= false;
325 for (const auto& reference_component
: record_metadata
.component()) {
326 if (reference_component
== proto_component
) {
327 has_reference_from_component
= true;
331 if (has_reference_from_component
)
332 metadata_list
->push_back(MakeAttachmentMetadata(id
, record_metadata
));
335 if (!db_iterator
->status().ok()) {
336 DVLOG(1) << "DB Iterator failed: status="
337 << db_iterator
->status().ToString();
338 result_code
= AttachmentStore::UNSPECIFIED_ERROR
;
342 PostCallback(base::Bind(callback
, result_code
, base::Passed(&metadata_list
)));
345 AttachmentStore::Result
OnDiskAttachmentStore::OpenOrCreate(
346 const base::FilePath
& path
) {
348 base::FilePath leveldb_path
= path
.Append(kLeveldbDirectory
);
351 scoped_ptr
<leveldb::DB
> db
;
352 leveldb::Options options
;
353 options
.create_if_missing
= true;
354 options
.reuse_logs
= leveldb_env::kDefaultLogReuseOptionValue
;
355 // TODO(pavely): crbug/424287 Consider adding info_log, block_cache and
356 // filter_policy to options.
357 leveldb::Status status
=
358 leveldb::DB::Open(options
, leveldb_path
.AsUTF8Unsafe(), &db_raw
);
360 DVLOG(1) << "DB::Open failed: status=" << status
.ToString()
361 << ", path=" << path
.AsUTF8Unsafe();
362 return AttachmentStore::UNSPECIFIED_ERROR
;
367 attachment_store_pb::StoreMetadata metadata
;
368 status
= ReadStoreMetadata(db
.get(), &metadata
);
369 if (!status
.ok() && !status
.IsNotFound()) {
370 DVLOG(1) << "ReadStoreMetadata failed: status=" << status
.ToString();
371 return AttachmentStore::UNSPECIFIED_ERROR
;
373 if (status
.IsNotFound()) {
374 // Brand new database.
375 metadata
.set_schema_version(kCurrentSchemaVersion
);
376 status
= WriteStoreMetadata(db
.get(), metadata
);
378 DVLOG(1) << "WriteStoreMetadata failed: status=" << status
.ToString();
379 return AttachmentStore::UNSPECIFIED_ERROR
;
384 // Upgrade code goes here.
386 if (metadata
.schema_version() != kCurrentSchemaVersion
) {
387 DVLOG(1) << "Unknown schema version: " << metadata
.schema_version();
388 return AttachmentStore::UNSPECIFIED_ERROR
;
392 return AttachmentStore::SUCCESS
;
395 scoped_ptr
<Attachment
> OnDiskAttachmentStore::ReadSingleAttachment(
396 const AttachmentId
& attachment_id
) {
397 scoped_ptr
<Attachment
> attachment
;
398 attachment_store_pb::RecordMetadata record_metadata
;
399 if (!ReadSingleRecordMetadata(attachment_id
, &record_metadata
)) {
400 return attachment
.Pass();
402 const std::string key
= MakeDataKeyFromAttachmentId(attachment_id
);
403 std::string data_str
;
404 leveldb::Status status
= db_
->Get(
405 MakeNonCachingReadOptions(), key
, &data_str
);
407 DVLOG(1) << "DB::Get for data failed: status=" << status
.ToString();
408 return attachment
.Pass();
410 scoped_refptr
<base::RefCountedMemory
> data
=
411 base::RefCountedString::TakeString(&data_str
);
412 uint32_t crc32c
= ComputeCrc32c(data
);
413 if (record_metadata
.has_crc32c()) {
414 if (record_metadata
.crc32c() != crc32c
) {
415 DVLOG(1) << "Attachment crc32c does not match value read from store";
416 return attachment
.Pass();
418 if (record_metadata
.crc32c() != attachment_id
.GetCrc32c()) {
419 DVLOG(1) << "Attachment crc32c does not match value in AttachmentId";
420 return attachment
.Pass();
424 new Attachment(Attachment::CreateFromParts(attachment_id
, data
)));
425 return attachment
.Pass();
428 bool OnDiskAttachmentStore::WriteSingleAttachment(
429 const Attachment
& attachment
,
430 AttachmentStore::Component component
) {
431 const std::string metadata_key
=
432 MakeMetadataKeyFromAttachmentId(attachment
.GetId());
433 const std::string data_key
= MakeDataKeyFromAttachmentId(attachment
.GetId());
435 std::string metadata_str
;
436 leveldb::Status status
=
437 db_
->Get(MakeCachingReadOptions(), metadata_key
, &metadata_str
);
439 // Entry exists, don't overwrite.
441 } else if (!status
.IsNotFound()) {
442 // Entry exists but failed to read.
443 DVLOG(1) << "DB::Get failed: status=" << status
.ToString();
446 DCHECK(status
.IsNotFound());
448 leveldb::WriteBatch write_batch
;
450 attachment_store_pb::RecordMetadata metadata
;
451 metadata
.set_attachment_size(attachment
.GetData()->size());
452 metadata
.set_crc32c(attachment
.GetCrc32c());
453 SetReferenceInRecordMetadata(&metadata
, ComponentToProto(component
));
454 metadata_str
= metadata
.SerializeAsString();
455 write_batch
.Put(metadata_key
, metadata_str
);
457 scoped_refptr
<base::RefCountedMemory
> data
= attachment
.GetData();
458 leveldb::Slice
data_slice(data
->front_as
<char>(), data
->size());
459 write_batch
.Put(data_key
, data_slice
);
461 status
= db_
->Write(MakeWriteOptions(), &write_batch
);
464 DVLOG(1) << "DB::Write failed: status=" << status
.ToString();
470 bool OnDiskAttachmentStore::ReadSingleRecordMetadata(
471 const AttachmentId
& attachment_id
,
472 attachment_store_pb::RecordMetadata
* record_metadata
) {
473 DCHECK(record_metadata
);
474 const std::string metadata_key
=
475 MakeMetadataKeyFromAttachmentId(attachment_id
);
476 std::string metadata_str
;
477 leveldb::Status status
=
478 db_
->Get(MakeCachingReadOptions(), metadata_key
, &metadata_str
);
480 DVLOG(1) << "DB::Get for metadata failed: status=" << status
.ToString();
483 if (!record_metadata
->ParseFromString(metadata_str
)) {
484 DVLOG(1) << "RecordMetadata::ParseFromString failed";
490 bool OnDiskAttachmentStore::WriteSingleRecordMetadata(
491 const AttachmentId
& attachment_id
,
492 const attachment_store_pb::RecordMetadata
& record_metadata
) {
493 const std::string metadata_key
=
494 MakeMetadataKeyFromAttachmentId(attachment_id
);
495 std::string metadata_str
;
496 metadata_str
= record_metadata
.SerializeAsString();
497 leveldb::Status status
=
498 db_
->Put(MakeWriteOptions(), metadata_key
, metadata_str
);
500 DVLOG(1) << "DB::Put failed: status=" << status
.ToString();
506 std::string
OnDiskAttachmentStore::MakeDataKeyFromAttachmentId(
507 const AttachmentId
& attachment_id
) {
508 std::string key
= kDataPrefix
+ attachment_id
.GetProto().unique_id();
512 std::string
OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId(
513 const AttachmentId
& attachment_id
) {
514 std::string key
= kMetadataPrefix
+ attachment_id
.GetProto().unique_id();
518 AttachmentMetadata
OnDiskAttachmentStore::MakeAttachmentMetadata(
519 const AttachmentId
& attachment_id
,
520 const attachment_store_pb::RecordMetadata
& record_metadata
) {
521 return AttachmentMetadata(attachment_id
, record_metadata
.attachment_size());
524 } // namespace syncer