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-"; | |
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. Don't verify checksums either, AttachmentStore will verify | |
47 // checksums separately. | |
48 read_options.fill_cache = false; | |
49 read_options.verify_checksums = false; | |
maniscalco
2014/10/30 16:40:20
WDYT about enabling leveldb checksums? Here's my
pavely
2014/10/30 18:28:36
I agree, letting leveldb can provide small additio
| |
50 return read_options; | |
51 } | |
52 | |
53 leveldb::ReadOptions MakeMetadataReadOptions() { | |
54 leveldb::ReadOptions read_options; | |
55 read_options.fill_cache = true; | |
56 read_options.verify_checksums = true; | |
57 return read_options; | |
58 } | |
59 | |
39 leveldb::Status ReadStoreMetadata( | 60 leveldb::Status ReadStoreMetadata( |
40 leveldb::DB* db, | 61 leveldb::DB* db, |
41 attachment_store_pb::AttachmentStoreMetadata* metadata) { | 62 attachment_store_pb::AttachmentStoreMetadata* metadata) { |
42 std::string data_str; | 63 std::string data_str; |
43 leveldb::ReadOptions read_options; | |
44 read_options.fill_cache = false; | |
45 read_options.verify_checksums = true; | |
46 | 64 |
47 leveldb::Status status = | 65 leveldb::Status status = |
48 db->Get(read_options, kDatabaseMetadataKey, &data_str); | 66 db->Get(MakeMetadataReadOptions(), kDatabaseMetadataKey, &data_str); |
49 if (!status.ok()) | 67 if (!status.ok()) |
50 return status; | 68 return status; |
51 if (!metadata->ParseFromString(data_str)) | 69 if (!metadata->ParseFromString(data_str)) |
52 return leveldb::Status::Corruption("Metadata record corruption"); | 70 return leveldb::Status::Corruption("Metadata record corruption"); |
53 return leveldb::Status::OK(); | 71 return leveldb::Status::OK(); |
54 } | 72 } |
55 | 73 |
56 leveldb::Status WriteStoreMetadata( | 74 leveldb::Status WriteStoreMetadata( |
57 leveldb::DB* db, | 75 leveldb::DB* db, |
58 const attachment_store_pb::AttachmentStoreMetadata& metadata) { | 76 const attachment_store_pb::AttachmentStoreMetadata& metadata) { |
(...skipping 13 matching lines...) Expand all Loading... | |
72 OnDiskAttachmentStore::~OnDiskAttachmentStore() { | 90 OnDiskAttachmentStore::~OnDiskAttachmentStore() { |
73 } | 91 } |
74 | 92 |
75 void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, | 93 void OnDiskAttachmentStore::Read(const AttachmentIdList& ids, |
76 const ReadCallback& callback) { | 94 const ReadCallback& callback) { |
77 DCHECK(CalledOnValidThread()); | 95 DCHECK(CalledOnValidThread()); |
78 DCHECK(db_); | 96 DCHECK(db_); |
79 scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); | 97 scoped_ptr<AttachmentMap> result_map(new AttachmentMap()); |
80 scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); | 98 scoped_ptr<AttachmentIdList> unavailable_attachments(new AttachmentIdList()); |
81 | 99 |
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(); | 100 AttachmentIdList::const_iterator iter = ids.begin(); |
89 const AttachmentIdList::const_iterator end = ids.end(); | 101 const AttachmentIdList::const_iterator end = ids.end(); |
90 for (; iter != end; ++iter) { | 102 for (; iter != end; ++iter) { |
91 const std::string key = MakeDataKeyFromAttachmentId(*iter); | 103 scoped_ptr<Attachment> attachment = ReadSingleAttachment(*iter); |
92 std::string data_str; | 104 if (attachment) { |
93 leveldb::Status status = db_->Get(read_options, key, &data_str); | 105 result_map->insert(std::make_pair(*iter, *attachment)); |
94 if (!status.ok()) { | 106 } else { |
95 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
96 unavailable_attachments->push_back(*iter); | 107 unavailable_attachments->push_back(*iter); |
97 continue; | |
98 } | 108 } |
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 } | 109 } |
104 | 110 |
105 Result result_code = | 111 Result result_code = |
106 unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; | 112 unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; |
107 callback_task_runner_->PostTask( | 113 callback_task_runner_->PostTask( |
108 FROM_HERE, | 114 FROM_HERE, |
109 base::Bind(callback, | 115 base::Bind(callback, |
110 result_code, | 116 result_code, |
111 base::Passed(&result_map), | 117 base::Passed(&result_map), |
112 base::Passed(&unavailable_attachments))); | 118 base::Passed(&unavailable_attachments))); |
113 } | 119 } |
114 | 120 |
115 void OnDiskAttachmentStore::Write(const AttachmentList& attachments, | 121 void OnDiskAttachmentStore::Write(const AttachmentList& attachments, |
116 const WriteCallback& callback) { | 122 const WriteCallback& callback) { |
117 DCHECK(CalledOnValidThread()); | 123 DCHECK(CalledOnValidThread()); |
118 DCHECK(db_); | 124 DCHECK(db_); |
119 Result result_code = SUCCESS; | 125 Result result_code = SUCCESS; |
120 | 126 |
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(); | 127 AttachmentList::const_iterator iter = attachments.begin(); |
128 const AttachmentList::const_iterator end = attachments.end(); | 128 const AttachmentList::const_iterator end = attachments.end(); |
129 for (; iter != end; ++iter) { | 129 for (; iter != end; ++iter) { |
130 const std::string key = MakeDataKeyFromAttachmentId(iter->GetId()); | 130 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; | 131 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 } | 132 } |
156 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); | 133 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
157 } | 134 } |
158 | 135 |
159 void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, | 136 void OnDiskAttachmentStore::Drop(const AttachmentIdList& ids, |
160 const DropCallback& callback) { | 137 const DropCallback& callback) { |
161 DCHECK(CalledOnValidThread()); | 138 DCHECK(CalledOnValidThread()); |
162 DCHECK(db_); | 139 DCHECK(db_); |
163 Result result_code = SUCCESS; | 140 Result result_code = SUCCESS; |
164 leveldb::WriteOptions write_options = MakeWriteOptions(); | 141 leveldb::WriteOptions write_options = MakeWriteOptions(); |
165 AttachmentIdList::const_iterator iter = ids.begin(); | 142 AttachmentIdList::const_iterator iter = ids.begin(); |
166 const AttachmentIdList::const_iterator end = ids.end(); | 143 const AttachmentIdList::const_iterator end = ids.end(); |
167 for (; iter != end; ++iter) { | 144 for (; iter != end; ++iter) { |
168 const std::string key = MakeDataKeyFromAttachmentId(*iter); | 145 leveldb::WriteBatch write_batch; |
169 leveldb::Status status = db_->Delete(write_options, key); | 146 write_batch.Delete(MakeDataKeyFromAttachmentId(*iter)); |
147 write_batch.Delete(MakeMetadataKeyFromAttachmentId(*iter)); | |
148 | |
149 leveldb::Status status = db_->Write(write_options, &write_batch); | |
170 if (!status.ok()) { | 150 if (!status.ok()) { |
171 // DB::Delete doesn't check if record exists, it returns ok just like | 151 // DB::Delete doesn't check if record exists, it returns ok just like |
172 // AttachmentStore::Drop should. | 152 // AttachmentStore::Drop should. |
173 DVLOG(1) << "DB::Delete failed: status=" << status.ToString(); | 153 DVLOG(1) << "DB::Write failed: status=" << status.ToString(); |
174 result_code = UNSPECIFIED_ERROR; | 154 result_code = UNSPECIFIED_ERROR; |
175 } | 155 } |
176 } | 156 } |
177 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); | 157 callback_task_runner_->PostTask(FROM_HERE, base::Bind(callback, result_code)); |
178 } | 158 } |
179 | 159 |
180 AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( | 160 AttachmentStore::Result OnDiskAttachmentStore::OpenOrCreate( |
181 const base::FilePath& path) { | 161 const base::FilePath& path) { |
182 DCHECK(CalledOnValidThread()); | 162 DCHECK(CalledOnValidThread()); |
183 DCHECK(!db_); | 163 DCHECK(!db_); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
220 | 200 |
221 if (metadata.schema_version() != kCurrentSchemaVersion) { | 201 if (metadata.schema_version() != kCurrentSchemaVersion) { |
222 DVLOG(1) << "Unknown schema version: " << metadata.schema_version(); | 202 DVLOG(1) << "Unknown schema version: " << metadata.schema_version(); |
223 return UNSPECIFIED_ERROR; | 203 return UNSPECIFIED_ERROR; |
224 } | 204 } |
225 | 205 |
226 db_ = db.Pass(); | 206 db_ = db.Pass(); |
227 return SUCCESS; | 207 return SUCCESS; |
228 } | 208 } |
229 | 209 |
210 scoped_ptr<Attachment> OnDiskAttachmentStore::ReadSingleAttachment( | |
211 const AttachmentId& attachment_id) { | |
212 scoped_ptr<Attachment> attachment; | |
213 | |
214 const std::string key = MakeDataKeyFromAttachmentId(attachment_id); | |
215 std::string data_str; | |
216 leveldb::Status status = db_->Get(MakeDataReadOptions(), key, &data_str); | |
217 if (status.ok()) { | |
218 scoped_refptr<base::RefCountedMemory> data = | |
219 base::RefCountedString::TakeString(&data_str); | |
220 attachment.reset( | |
221 new Attachment(Attachment::CreateWithId(attachment_id, data))); | |
222 } else { | |
223 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
224 } | |
225 return attachment.Pass(); | |
226 } | |
227 | |
228 bool OnDiskAttachmentStore::WriteSingleAttachment( | |
229 const Attachment& attachment) { | |
230 const std::string metadata_key = | |
231 MakeMetadataKeyFromAttachmentId(attachment.GetId()); | |
232 const std::string data_key = MakeDataKeyFromAttachmentId(attachment.GetId()); | |
233 | |
234 std::string metadata_str; | |
235 leveldb::Status status = | |
236 db_->Get(MakeMetadataReadOptions(), metadata_key, &metadata_str); | |
237 if (status.ok()) { | |
238 // Entry exists, don't overwrite. | |
239 return true; | |
240 } else if (!status.IsNotFound()) { | |
241 // Entry exists but failed to read. | |
242 DVLOG(1) << "DB::Get failed: status=" << status.ToString(); | |
243 return false; | |
244 } | |
245 DCHECK(status.IsNotFound()); | |
246 | |
247 leveldb::WriteBatch write_batch; | |
248 // Write metadata. | |
249 attachment_store_pb::AttachmentRecordMetadata metadata; | |
250 metadata.set_attachment_size(attachment.GetData()->size()); | |
251 metadata_str = metadata.SerializeAsString(); | |
252 write_batch.Put(metadata_key, metadata_str); | |
253 // Write data. | |
254 scoped_refptr<base::RefCountedMemory> data = attachment.GetData(); | |
255 leveldb::Slice data_slice(data->front_as<char>(), data->size()); | |
256 write_batch.Put(data_key, data_slice); | |
257 | |
258 status = db_->Write(MakeWriteOptions(), &write_batch); | |
259 if (!status.ok()) { | |
260 // Failed to write. | |
261 DVLOG(1) << "DB::Write failed: status=" << status.ToString(); | |
262 return false; | |
263 } | |
264 return true; | |
265 } | |
266 | |
230 std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId( | 267 std::string OnDiskAttachmentStore::MakeDataKeyFromAttachmentId( |
231 const AttachmentId& attachment_id) { | 268 const AttachmentId& attachment_id) { |
232 std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); | 269 std::string key = kDataPrefix + attachment_id.GetProto().unique_id(); |
233 return key; | 270 return key; |
234 } | 271 } |
235 | 272 |
273 std::string OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId( | |
274 const AttachmentId& attachment_id) { | |
275 std::string key = kMetadataPrefix + attachment_id.GetProto().unique_id(); | |
276 return key; | |
277 } | |
278 | |
236 } // namespace syncer | 279 } // namespace syncer |
OLD | NEW |