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 |