Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1086)

Side by Side Diff: google_apis/gcm/engine/gcm_store_impl.cc

Issue 1548673002: Switch to standard integer types in google_apis/. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 "google_apis/gcm/engine/gcm_store_impl.h" 5 #include "google_apis/gcm/engine/gcm_store_impl.h"
6 6
7 #include "base/basictypes.h"
8 #include "base/bind.h" 7 #include "base/bind.h"
9 #include "base/callback.h" 8 #include "base/callback.h"
10 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
11 #include "base/files/file_util.h" 10 #include "base/files/file_util.h"
12 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/macros.h"
13 #include "base/metrics/histogram_macros.h" 13 #include "base/metrics/histogram_macros.h"
14 #include "base/profiler/scoped_tracker.h" 14 #include "base/profiler/scoped_tracker.h"
15 #include "base/sequenced_task_runner.h" 15 #include "base/sequenced_task_runner.h"
16 #include "base/stl_util.h" 16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h" 17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_piece.h" 18 #include "base/strings/string_piece.h"
19 #include "base/strings/string_tokenizer.h" 19 #include "base/strings/string_tokenizer.h"
20 #include "base/strings/string_util.h" 20 #include "base/strings/string_util.h"
21 #include "base/thread_task_runner_handle.h" 21 #include "base/thread_task_runner_handle.h"
22 #include "base/time/time.h" 22 #include "base/time/time.h"
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 : public base::RefCountedThreadSafe<GCMStoreImpl::Backend> { 182 : public base::RefCountedThreadSafe<GCMStoreImpl::Backend> {
183 public: 183 public:
184 Backend(const base::FilePath& path, 184 Backend(const base::FilePath& path,
185 scoped_refptr<base::SequencedTaskRunner> foreground_runner, 185 scoped_refptr<base::SequencedTaskRunner> foreground_runner,
186 scoped_ptr<Encryptor> encryptor); 186 scoped_ptr<Encryptor> encryptor);
187 187
188 // Blocking implementations of GCMStoreImpl methods. 188 // Blocking implementations of GCMStoreImpl methods.
189 void Load(StoreOpenMode open_mode, const LoadCallback& callback); 189 void Load(StoreOpenMode open_mode, const LoadCallback& callback);
190 void Close(); 190 void Close();
191 void Destroy(const UpdateCallback& callback); 191 void Destroy(const UpdateCallback& callback);
192 void SetDeviceCredentials(uint64 device_android_id, 192 void SetDeviceCredentials(uint64_t device_android_id,
193 uint64 device_security_token, 193 uint64_t device_security_token,
194 const UpdateCallback& callback); 194 const UpdateCallback& callback);
195 void AddRegistration(const std::string& serialized_key, 195 void AddRegistration(const std::string& serialized_key,
196 const std::string& serialized_value, 196 const std::string& serialized_value,
197 const UpdateCallback& callback); 197 const UpdateCallback& callback);
198 void RemoveRegistration(const std::string& serialized_key, 198 void RemoveRegistration(const std::string& serialized_key,
199 const UpdateCallback& callback); 199 const UpdateCallback& callback);
200 void AddIncomingMessage(const std::string& persistent_id, 200 void AddIncomingMessage(const std::string& persistent_id,
201 const UpdateCallback& callback); 201 const UpdateCallback& callback);
202 void RemoveIncomingMessages(const PersistentIdList& persistent_ids, 202 void RemoveIncomingMessages(const PersistentIdList& persistent_ids,
203 const UpdateCallback& callback); 203 const UpdateCallback& callback);
204 void AddOutgoingMessage(const std::string& persistent_id, 204 void AddOutgoingMessage(const std::string& persistent_id,
205 const MCSMessage& message, 205 const MCSMessage& message,
206 const UpdateCallback& callback); 206 const UpdateCallback& callback);
207 void RemoveOutgoingMessages( 207 void RemoveOutgoingMessages(
208 const PersistentIdList& persistent_ids, 208 const PersistentIdList& persistent_ids,
209 const base::Callback<void(bool, const AppIdToMessageCountMap&)> 209 const base::Callback<void(bool, const AppIdToMessageCountMap&)>
210 callback); 210 callback);
211 void AddUserSerialNumber(const std::string& username, 211 void AddUserSerialNumber(const std::string& username,
212 int64 serial_number, 212 int64_t serial_number,
213 const UpdateCallback& callback); 213 const UpdateCallback& callback);
214 void RemoveUserSerialNumber(const std::string& username, 214 void RemoveUserSerialNumber(const std::string& username,
215 const UpdateCallback& callback); 215 const UpdateCallback& callback);
216 void SetLastCheckinInfo(const base::Time& time, 216 void SetLastCheckinInfo(const base::Time& time,
217 const std::set<std::string>& accounts, 217 const std::set<std::string>& accounts,
218 const UpdateCallback& callback); 218 const UpdateCallback& callback);
219 void SetGServicesSettings( 219 void SetGServicesSettings(
220 const std::map<std::string, std::string>& settings, 220 const std::map<std::string, std::string>& settings,
221 const std::string& digest, 221 const std::string& digest,
222 const UpdateCallback& callback); 222 const UpdateCallback& callback);
(...skipping 15 matching lines...) Expand all
238 const UpdateCallback& callback); 238 const UpdateCallback& callback);
239 void SetValue(const std::string& key, 239 void SetValue(const std::string& key,
240 const std::string& value, 240 const std::string& value,
241 const UpdateCallback& callback); 241 const UpdateCallback& callback);
242 242
243 private: 243 private:
244 friend class base::RefCountedThreadSafe<Backend>; 244 friend class base::RefCountedThreadSafe<Backend>;
245 ~Backend(); 245 ~Backend();
246 246
247 LoadStatus OpenStoreAndLoadData(StoreOpenMode open_mode, LoadResult* result); 247 LoadStatus OpenStoreAndLoadData(StoreOpenMode open_mode, LoadResult* result);
248 bool LoadDeviceCredentials(uint64* android_id, uint64* security_token); 248 bool LoadDeviceCredentials(uint64_t* android_id, uint64_t* security_token);
249 bool LoadRegistrations(std::map<std::string, std::string>* registrations); 249 bool LoadRegistrations(std::map<std::string, std::string>* registrations);
250 bool LoadIncomingMessages(std::vector<std::string>* incoming_messages); 250 bool LoadIncomingMessages(std::vector<std::string>* incoming_messages);
251 bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages); 251 bool LoadOutgoingMessages(OutgoingMessageMap* outgoing_messages);
252 bool LoadLastCheckinInfo(base::Time* last_checkin_time, 252 bool LoadLastCheckinInfo(base::Time* last_checkin_time,
253 std::set<std::string>* accounts); 253 std::set<std::string>* accounts);
254 bool LoadGServicesSettings(std::map<std::string, std::string>* settings, 254 bool LoadGServicesSettings(std::map<std::string, std::string>* settings,
255 std::string* digest); 255 std::string* digest);
256 bool LoadAccountMappingInfo(AccountMappings* account_mappings); 256 bool LoadAccountMappingInfo(AccountMappings* account_mappings);
257 bool LoadLastTokenFetchTime(base::Time* last_token_fetch_time); 257 bool LoadLastTokenFetchTime(base::Time* last_token_fetch_time);
258 bool LoadHeartbeatIntervals(std::map<std::string, int>* heartbeat_intervals); 258 bool LoadHeartbeatIntervals(std::map<std::string, int>* heartbeat_intervals);
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
359 for (const auto& registration : result->registrations) { 359 for (const auto& registration : result->registrations) {
360 if (base::StartsWith(registration.first, "iid-", 360 if (base::StartsWith(registration.first, "iid-",
361 base::CompareCase::SENSITIVE)) 361 base::CompareCase::SENSITIVE))
362 instance_id_token_count++; 362 instance_id_token_count++;
363 else 363 else
364 gcm_registration_count++; 364 gcm_registration_count++;
365 } 365 }
366 366
367 // Only record histograms if GCM had already been set up for this device. 367 // Only record histograms if GCM had already been set up for this device.
368 if (result->device_android_id != 0 && result->device_security_token != 0) { 368 if (result->device_android_id != 0 && result->device_security_token != 0) {
369 int64 file_size = 0; 369 int64_t file_size = 0;
370 if (base::GetFileSize(path_, &file_size)) { 370 if (base::GetFileSize(path_, &file_size)) {
371 UMA_HISTOGRAM_COUNTS("GCM.StoreSizeKB", 371 UMA_HISTOGRAM_COUNTS("GCM.StoreSizeKB",
372 static_cast<int>(file_size / 1024)); 372 static_cast<int>(file_size / 1024));
373 } 373 }
374 374
375 UMA_HISTOGRAM_COUNTS("GCM.RestoredRegistrations", gcm_registration_count); 375 UMA_HISTOGRAM_COUNTS("GCM.RestoredRegistrations", gcm_registration_count);
376 UMA_HISTOGRAM_COUNTS("GCM.RestoredOutgoingMessages", 376 UMA_HISTOGRAM_COUNTS("GCM.RestoredOutgoingMessages",
377 result->outgoing_messages.size()); 377 result->outgoing_messages.size());
378 UMA_HISTOGRAM_COUNTS("GCM.RestoredIncomingMessages", 378 UMA_HISTOGRAM_COUNTS("GCM.RestoredIncomingMessages",
379 result->incoming_messages.size()); 379 result->incoming_messages.size());
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options()); 411 leveldb::DestroyDB(path_.AsUTF8Unsafe(), leveldb::Options());
412 if (s.ok()) { 412 if (s.ok()) {
413 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true)); 413 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, true));
414 return; 414 return;
415 } 415 }
416 LOG(ERROR) << "Destroy failed: " << s.ToString(); 416 LOG(ERROR) << "Destroy failed: " << s.ToString();
417 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); 417 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
418 } 418 }
419 419
420 void GCMStoreImpl::Backend::SetDeviceCredentials( 420 void GCMStoreImpl::Backend::SetDeviceCredentials(
421 uint64 device_android_id, 421 uint64_t device_android_id,
422 uint64 device_security_token, 422 uint64_t device_security_token,
423 const UpdateCallback& callback) { 423 const UpdateCallback& callback) {
424 DVLOG(1) << "Saving device credentials with AID " << device_android_id; 424 DVLOG(1) << "Saving device credentials with AID " << device_android_id;
425 if (!db_.get()) { 425 if (!db_.get()) {
426 LOG(ERROR) << "GCMStore db doesn't exist."; 426 LOG(ERROR) << "GCMStore db doesn't exist.";
427 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false)); 427 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
428 return; 428 return;
429 } 429 }
430 430
431 leveldb::WriteOptions write_options; 431 leveldb::WriteOptions write_options;
432 write_options.sync = true; 432 write_options.sync = true;
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after
629 false, 629 false,
630 AppIdToMessageCountMap())); 630 AppIdToMessageCountMap()));
631 } 631 }
632 632
633 void GCMStoreImpl::Backend::SetLastCheckinInfo( 633 void GCMStoreImpl::Backend::SetLastCheckinInfo(
634 const base::Time& time, 634 const base::Time& time,
635 const std::set<std::string>& accounts, 635 const std::set<std::string>& accounts,
636 const UpdateCallback& callback) { 636 const UpdateCallback& callback) {
637 leveldb::WriteBatch write_batch; 637 leveldb::WriteBatch write_batch;
638 638
639 int64 last_checkin_time_internal = time.ToInternalValue(); 639 int64_t last_checkin_time_internal = time.ToInternalValue();
640 write_batch.Put(MakeSlice(kLastCheckinTimeKey), 640 write_batch.Put(MakeSlice(kLastCheckinTimeKey),
641 MakeSlice(base::Int64ToString(last_checkin_time_internal))); 641 MakeSlice(base::Int64ToString(last_checkin_time_internal)));
642 642
643 std::string serialized_accounts; 643 std::string serialized_accounts;
644 for (std::set<std::string>::iterator iter = accounts.begin(); 644 for (std::set<std::string>::iterator iter = accounts.begin();
645 iter != accounts.end(); 645 iter != accounts.end();
646 ++iter) { 646 ++iter) {
647 serialized_accounts += *iter; 647 serialized_accounts += *iter;
648 serialized_accounts += ","; 648 serialized_accounts += ",";
649 } 649 }
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
892 write_options.sync = true; 892 write_options.sync = true;
893 893
894 const leveldb::Status s = 894 const leveldb::Status s =
895 db_->Put(write_options, MakeSlice(key), MakeSlice(value)); 895 db_->Put(write_options, MakeSlice(key), MakeSlice(value));
896 896
897 if (!s.ok()) 897 if (!s.ok())
898 LOG(ERROR) << "LevelDB had problems injecting a value: " << s.ToString(); 898 LOG(ERROR) << "LevelDB had problems injecting a value: " << s.ToString();
899 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); 899 foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
900 } 900 }
901 901
902 bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id, 902 bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64_t* android_id,
903 uint64* security_token) { 903 uint64_t* security_token) {
904 leveldb::ReadOptions read_options; 904 leveldb::ReadOptions read_options;
905 read_options.verify_checksums = true; 905 read_options.verify_checksums = true;
906 906
907 std::string result; 907 std::string result;
908 leveldb::Status s = db_->Get(read_options, MakeSlice(kDeviceAIDKey), &result); 908 leveldb::Status s = db_->Get(read_options, MakeSlice(kDeviceAIDKey), &result);
909 if (s.ok()) { 909 if (s.ok()) {
910 if (!base::StringToUint64(result, android_id)) { 910 if (!base::StringToUint64(result, android_id)) {
911 LOG(ERROR) << "Failed to restore device id."; 911 LOG(ERROR) << "Failed to restore device id.";
912 return false; 912 return false;
913 } 913 }
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
984 984
985 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options)); 985 scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
986 for (iter->Seek(MakeSlice(kOutgoingMsgKeyStart)); 986 for (iter->Seek(MakeSlice(kOutgoingMsgKeyStart));
987 iter->Valid() && iter->key().ToString() < kOutgoingMsgKeyEnd; 987 iter->Valid() && iter->key().ToString() < kOutgoingMsgKeyEnd;
988 iter->Next()) { 988 iter->Next()) {
989 leveldb::Slice s = iter->value(); 989 leveldb::Slice s = iter->value();
990 if (s.size() <= 1) { 990 if (s.size() <= 1) {
991 LOG(ERROR) << "Error reading incoming message with key " << s.ToString(); 991 LOG(ERROR) << "Error reading incoming message with key " << s.ToString();
992 return false; 992 return false;
993 } 993 }
994 uint8 tag = iter->value().data()[0]; 994 uint8_t tag = iter->value().data()[0];
995 std::string id = ParseOutgoingKey(iter->key().ToString()); 995 std::string id = ParseOutgoingKey(iter->key().ToString());
996 scoped_ptr<google::protobuf::MessageLite> message( 996 scoped_ptr<google::protobuf::MessageLite> message(
997 BuildProtobufFromTag(tag)); 997 BuildProtobufFromTag(tag));
998 if (!message.get() || 998 if (!message.get() ||
999 !message->ParseFromString(iter->value().ToString().substr(1))) { 999 !message->ParseFromString(iter->value().ToString().substr(1))) {
1000 LOG(ERROR) << "Failed to parse outgoing message with id " << id 1000 LOG(ERROR) << "Failed to parse outgoing message with id " << id
1001 << " and tag " << tag; 1001 << " and tag " << tag;
1002 return false; 1002 return false;
1003 } 1003 }
1004 DVLOG(1) << "Found outgoing message with id " << id << " of type " 1004 DVLOG(1) << "Found outgoing message with id " << id << " of type "
1005 << base::UintToString(tag); 1005 << base::UintToString(tag);
1006 (*outgoing_messages)[id] = make_linked_ptr(message.release()); 1006 (*outgoing_messages)[id] = make_linked_ptr(message.release());
1007 } 1007 }
1008 1008
1009 return true; 1009 return true;
1010 } 1010 }
1011 1011
1012 bool GCMStoreImpl::Backend::LoadLastCheckinInfo( 1012 bool GCMStoreImpl::Backend::LoadLastCheckinInfo(
1013 base::Time* last_checkin_time, 1013 base::Time* last_checkin_time,
1014 std::set<std::string>* accounts) { 1014 std::set<std::string>* accounts) {
1015 leveldb::ReadOptions read_options; 1015 leveldb::ReadOptions read_options;
1016 read_options.verify_checksums = true; 1016 read_options.verify_checksums = true;
1017 1017
1018 std::string result; 1018 std::string result;
1019 leveldb::Status s = db_->Get(read_options, 1019 leveldb::Status s = db_->Get(read_options,
1020 MakeSlice(kLastCheckinTimeKey), 1020 MakeSlice(kLastCheckinTimeKey),
1021 &result); 1021 &result);
1022 int64 time_internal = 0LL; 1022 int64_t time_internal = 0LL;
1023 if (s.ok() && !base::StringToInt64(result, &time_internal)) { 1023 if (s.ok() && !base::StringToInt64(result, &time_internal)) {
1024 LOG(ERROR) << "Failed to restore last checkin time. Using default = 0."; 1024 LOG(ERROR) << "Failed to restore last checkin time. Using default = 0.";
1025 time_internal = 0LL; 1025 time_internal = 0LL;
1026 } 1026 }
1027 1027
1028 // In case we cannot read last checkin time, we default it to 0, as we don't 1028 // In case we cannot read last checkin time, we default it to 0, as we don't
1029 // want that situation to cause the whole load to fail. 1029 // want that situation to cause the whole load to fail.
1030 *last_checkin_time = base::Time::FromInternalValue(time_internal); 1030 *last_checkin_time = base::Time::FromInternalValue(time_internal);
1031 1031
1032 accounts->clear(); 1032 accounts->clear();
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
1093 } 1093 }
1094 1094
1095 bool GCMStoreImpl::Backend::LoadLastTokenFetchTime( 1095 bool GCMStoreImpl::Backend::LoadLastTokenFetchTime(
1096 base::Time* last_token_fetch_time) { 1096 base::Time* last_token_fetch_time) {
1097 leveldb::ReadOptions read_options; 1097 leveldb::ReadOptions read_options;
1098 read_options.verify_checksums = true; 1098 read_options.verify_checksums = true;
1099 1099
1100 std::string result; 1100 std::string result;
1101 leveldb::Status s = 1101 leveldb::Status s =
1102 db_->Get(read_options, MakeSlice(kLastTokenFetchTimeKey), &result); 1102 db_->Get(read_options, MakeSlice(kLastTokenFetchTimeKey), &result);
1103 int64 time_internal = 0LL; 1103 int64_t time_internal = 0LL;
1104 if (s.ok() && !base::StringToInt64(result, &time_internal)) { 1104 if (s.ok() && !base::StringToInt64(result, &time_internal)) {
1105 LOG(ERROR) << 1105 LOG(ERROR) <<
1106 "Failed to restore last token fetching time. Using default = 0."; 1106 "Failed to restore last token fetching time. Using default = 0.";
1107 time_internal = 0LL; 1107 time_internal = 0LL;
1108 } 1108 }
1109 1109
1110 // In case we cannot read last token fetching time, we default it to 0. 1110 // In case we cannot read last token fetching time, we default it to 0.
1111 *last_token_fetch_time = base::Time::FromInternalValue(time_internal); 1111 *last_token_fetch_time = base::Time::FromInternalValue(time_internal);
1112 1112
1113 return true; 1113 return true;
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
1190 FROM_HERE, 1190 FROM_HERE,
1191 base::Bind(&GCMStoreImpl::Backend::Close, backend_)); 1191 base::Bind(&GCMStoreImpl::Backend::Close, backend_));
1192 } 1192 }
1193 1193
1194 void GCMStoreImpl::Destroy(const UpdateCallback& callback) { 1194 void GCMStoreImpl::Destroy(const UpdateCallback& callback) {
1195 blocking_task_runner_->PostTask( 1195 blocking_task_runner_->PostTask(
1196 FROM_HERE, 1196 FROM_HERE,
1197 base::Bind(&GCMStoreImpl::Backend::Destroy, backend_, callback)); 1197 base::Bind(&GCMStoreImpl::Backend::Destroy, backend_, callback));
1198 } 1198 }
1199 1199
1200 void GCMStoreImpl::SetDeviceCredentials(uint64 device_android_id, 1200 void GCMStoreImpl::SetDeviceCredentials(uint64_t device_android_id,
1201 uint64 device_security_token, 1201 uint64_t device_security_token,
1202 const UpdateCallback& callback) { 1202 const UpdateCallback& callback) {
1203 blocking_task_runner_->PostTask( 1203 blocking_task_runner_->PostTask(
1204 FROM_HERE, 1204 FROM_HERE,
1205 base::Bind(&GCMStoreImpl::Backend::SetDeviceCredentials, 1205 base::Bind(&GCMStoreImpl::Backend::SetDeviceCredentials,
1206 backend_, 1206 backend_,
1207 device_android_id, 1207 device_android_id,
1208 device_security_token, 1208 device_security_token,
1209 callback)); 1209 callback));
1210 } 1210 }
1211 1211
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
1474 removed_message_counts.begin(); 1474 removed_message_counts.begin();
1475 iter != removed_message_counts.end(); ++iter) { 1475 iter != removed_message_counts.end(); ++iter) {
1476 DCHECK_NE(app_message_counts_.count(iter->first), 0U); 1476 DCHECK_NE(app_message_counts_.count(iter->first), 0U);
1477 app_message_counts_[iter->first] -= iter->second; 1477 app_message_counts_[iter->first] -= iter->second;
1478 DCHECK_GE(app_message_counts_[iter->first], 0); 1478 DCHECK_GE(app_message_counts_[iter->first], 0);
1479 } 1479 }
1480 callback.Run(true); 1480 callback.Run(true);
1481 } 1481 }
1482 1482
1483 } // namespace gcm 1483 } // namespace gcm
OLDNEW
« no previous file with comments | « google_apis/gcm/engine/gcm_store_impl.h ('k') | google_apis/gcm/engine/gcm_store_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698