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

Side by Side Diff: sync/syncable/directory.cc

Issue 11441026: [Sync] Add support for loading, updating and querying delete journals in (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase and update Created 8 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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/syncable/directory.h" 5 #include "sync/syncable/directory.h"
6 6
7 #include "base/debug/trace_event.h" 7 #include "base/debug/trace_event.h"
8 #include "base/perftimer.h" 8 #include "base/perftimer.h"
9 #include "base/stl_util.h" 9 #include "base/stl_util.h"
10 #include "base/string_number_conversions.h" 10 #include "base/string_number_conversions.h"
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
94 download_progress[model_type].set_data_type_id( 94 download_progress[model_type].set_data_type_id(
95 GetSpecificsFieldNumberFromModelType(model_type)); 95 GetSpecificsFieldNumberFromModelType(model_type));
96 // An empty-string token indicates no prior knowledge. 96 // An empty-string token indicates no prior knowledge.
97 download_progress[model_type].set_token(std::string()); 97 download_progress[model_type].set_token(std::string());
98 } 98 }
99 99
100 Directory::SaveChangesSnapshot::SaveChangesSnapshot() 100 Directory::SaveChangesSnapshot::SaveChangesSnapshot()
101 : kernel_info_status(KERNEL_SHARE_INFO_INVALID) { 101 : kernel_info_status(KERNEL_SHARE_INFO_INVALID) {
102 } 102 }
103 103
104 Directory::SaveChangesSnapshot::~SaveChangesSnapshot() {} 104 Directory::SaveChangesSnapshot::~SaveChangesSnapshot() {
105 STLDeleteElements(&dirty_metas);
106 STLDeleteElements(&delete_journals);
107 }
105 108
106 Directory::Kernel::Kernel( 109 Directory::Kernel::Kernel(
107 const std::string& name, 110 const std::string& name,
108 const KernelLoadInfo& info, DirectoryChangeDelegate* delegate, 111 const KernelLoadInfo& info, DirectoryChangeDelegate* delegate,
109 const WeakHandle<TransactionObserver>& transaction_observer) 112 const WeakHandle<TransactionObserver>& transaction_observer)
110 : next_write_transaction_id(0), 113 : next_write_transaction_id(0),
111 name(name), 114 name(name),
112 metahandles_index(new Directory::MetahandlesIndex), 115 metahandles_index(new Directory::MetahandlesIndex),
113 ids_index(new Directory::IdsIndex), 116 ids_index(new Directory::IdsIndex),
114 parent_id_child_index(new Directory::ParentIdChildIndex), 117 parent_id_child_index(new Directory::ParentIdChildIndex),
115 client_tag_index(new Directory::ClientTagIndex), 118 client_tag_index(new Directory::ClientTagIndex),
116 unsynced_metahandles(new MetahandleSet), 119 unsynced_metahandles(new MetahandleSet),
117 dirty_metahandles(new MetahandleSet), 120 dirty_metahandles(new MetahandleSet),
118 metahandles_to_purge(new MetahandleSet), 121 metahandles_to_purge(new MetahandleSet),
119 info_status(Directory::KERNEL_SHARE_INFO_VALID), 122 info_status(Directory::KERNEL_SHARE_INFO_VALID),
120 persisted_info(info.kernel_info), 123 persisted_info(info.kernel_info),
121 cache_guid(info.cache_guid), 124 cache_guid(info.cache_guid),
122 next_metahandle(info.max_metahandle + 1), 125 next_metahandle(info.max_metahandle + 1),
123 delegate(delegate), 126 delegate(delegate),
124 transaction_observer(transaction_observer) { 127 transaction_observer(transaction_observer),
128 delete_journals_(new Directory::IdsIndex),
129 delete_journals_to_purge_(new MetahandleSet) {
125 DCHECK(delegate); 130 DCHECK(delegate);
126 DCHECK(transaction_observer.IsInitialized()); 131 DCHECK(transaction_observer.IsInitialized());
127 } 132 }
128 133
129 Directory::Kernel::~Kernel() { 134 Directory::Kernel::~Kernel() {
130 delete unsynced_metahandles; 135 delete unsynced_metahandles;
131 delete dirty_metahandles; 136 delete dirty_metahandles;
132 delete metahandles_to_purge; 137 delete metahandles_to_purge;
133 delete parent_id_child_index; 138 delete parent_id_child_index;
134 delete client_tag_index; 139 delete client_tag_index;
135 delete ids_index; 140 delete ids_index;
136 STLDeleteElements(metahandles_index); 141 STLDeleteElements(metahandles_index);
137 delete metahandles_index; 142 delete metahandles_index;
143 STLDeleteElements(delete_journals_);
144 delete delete_journals_;
145 delete delete_journals_to_purge_;
138 } 146 }
139 147
140 Directory::Directory( 148 Directory::Directory(
141 DirectoryBackingStore* store, 149 DirectoryBackingStore* store,
142 UnrecoverableErrorHandler* unrecoverable_error_handler, 150 UnrecoverableErrorHandler* unrecoverable_error_handler,
143 ReportUnrecoverableErrorFunction report_unrecoverable_error_function, 151 ReportUnrecoverableErrorFunction report_unrecoverable_error_function,
144 NigoriHandler* nigori_handler, 152 NigoriHandler* nigori_handler,
145 Cryptographer* cryptographer) 153 Cryptographer* cryptographer)
146 : kernel_(NULL), 154 : kernel_(NULL),
147 store_(store), 155 store_(store),
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
189 } 197 }
190 DCHECK(!entry->is_dirty()); 198 DCHECK(!entry->is_dirty());
191 } 199 }
192 } 200 }
193 201
194 DirOpenResult Directory::OpenImpl( 202 DirOpenResult Directory::OpenImpl(
195 const string& name, 203 const string& name,
196 DirectoryChangeDelegate* delegate, 204 DirectoryChangeDelegate* delegate,
197 const WeakHandle<TransactionObserver>& 205 const WeakHandle<TransactionObserver>&
198 transaction_observer) { 206 transaction_observer) {
199
200 KernelLoadInfo info; 207 KernelLoadInfo info;
201 // Temporary indices before kernel_ initialized in case Load fails. We 0(1) 208 // Temporary indices before kernel_ initialized in case Load fails. We 0(1)
202 // swap these later. 209 // swap these later.
203 MetahandlesIndex metas_bucket; 210 MetahandlesIndex metas_bucket;
204 DirOpenResult result = store_->Load(&metas_bucket, &info); 211 IdsIndex delete_journals;
212
213 DirOpenResult result = store_->Load(&metas_bucket, &delete_journals, &info);
205 if (OPENED != result) 214 if (OPENED != result)
206 return result; 215 return result;
207 216
208 kernel_ = new Kernel(name, info, delegate, transaction_observer); 217 kernel_ = new Kernel(name, info, delegate, transaction_observer);
209 kernel_->metahandles_index->swap(metas_bucket); 218 kernel_->metahandles_index->swap(metas_bucket);
219 kernel_->delete_journals_->swap(delete_journals);
210 InitializeIndices(); 220 InitializeIndices();
211 221
212 // Write back the share info to reserve some space in 'next_id'. This will 222 // Write back the share info to reserve some space in 'next_id'. This will
213 // prevent local ID reuse in the case of an early crash. See the comments in 223 // prevent local ID reuse in the case of an early crash. See the comments in
214 // TakeSnapshotForSaveChanges() or crbug.com/142987 for more information. 224 // TakeSnapshotForSaveChanges() or crbug.com/142987 for more information.
215 kernel_->info_status = KERNEL_SHARE_INFO_DIRTY; 225 kernel_->info_status = KERNEL_SHARE_INFO_DIRTY;
216 if (!SaveChanges()) 226 if (!SaveChanges())
217 return FAILED_INITIAL_WRITE; 227 return FAILED_INITIAL_WRITE;
218 228
219 return OPENED; 229 return OPENED;
220 } 230 }
221 231
222 void Directory::Close() { 232 void Directory::Close() {
223 store_.reset(); 233 store_.reset();
224 if (kernel_) { 234 if (kernel_) {
225 delete kernel_; 235 delete kernel_;
226 kernel_ = NULL; 236 kernel_ = NULL;
227 } 237 }
228 } 238 }
229 239
230 void Directory::OnUnrecoverableError(const BaseTransaction* trans, 240 void Directory::OnUnrecoverableError(const BaseTransaction* trans,
231 const tracked_objects::Location& location, 241 const tracked_objects::Location& location,
232 const std::string & message) { 242 const std::string & message) {
233 DCHECK(trans != NULL); 243 DCHECK(trans != NULL);
234 unrecoverable_error_set_ = true; 244 unrecoverable_error_set_ = true;
235 unrecoverable_error_handler_->OnUnrecoverableError(location, 245 unrecoverable_error_handler_->OnUnrecoverableError(location,
236 message); 246 message);
237 } 247 }
238 248
249 void Directory::UpdateDeleteJournals(BaseTransaction* trans,
250 bool was_deleted,
251 const EntryKernel* entry) {
tim (not reviewing) 2012/12/13 02:48:10 How come this can't be a ref?
haitaol1 2012/12/13 21:34:21 Done.
252 if (!IsDeleteJournalEnabledForType(entry->GetServerModelType()))
253 return;
254
255 ScopedKernelLock lock(this);
256 IdsIndex::const_iterator it =
257 kernel_->delete_journals_->find(const_cast<EntryKernel*>(entry));
258
259 if (entry->ref(SERVER_IS_DEL)) {
260 if (it == kernel_->delete_journals_->end()) {
261 // New delete.
262 EntryKernel* t = new EntryKernel(*entry);
263 kernel_->delete_journals_->insert(t);
264 kernel_->delete_journals_to_purge_->erase(t->ref(META_HANDLE));
265 }
266 } else {
267 // Undelete. This could happen in two cases:
268 // * An entry was actually deleted and undeleted: was_deleted = true.
269 // * A data type was broken in last sync session and all its entries
270 // were duplicated in delete journals. On restart, entries are recreated
271 // from downloads and recreation calls UpdateDeleteJournals() to remove
272 // live entries from delete journals, thus only deleted entries remain in
273 // journals.
274 if (it != kernel_->delete_journals_->end()) {
275 kernel_->delete_journals_to_purge_->insert((*it)->ref(META_HANDLE));
276 delete *it;
277 kernel_->delete_journals_->erase(it);
278 } else if (was_deleted) {
279 kernel_->delete_journals_to_purge_->insert((*it)->ref(META_HANDLE));
280 }
281 }
282 }
283
284 void Directory::GetDeleteJournals(BaseTransaction* trans,
285 ModelType type,
286 EntryKernelSet* deleted_entries) {
287 ScopedKernelLock lock(this);
288 DCHECK(!passive_delete_journal_types_.Has(type));
289 for (IdsIndex::const_iterator it = kernel_->delete_journals_->begin();
290 it != kernel_->delete_journals_->end(); ++it) {
291 if ((*it)->GetServerModelType() == type)
292 deleted_entries->insert(*it);
293 }
294 passive_delete_journal_types_.Put(type);
295 }
296
297 void Directory::PurgeDeleteJournals(BaseTransaction* trans,
298 const MetahandleSet& to_purge) {
299 ScopedKernelLock lock(this);
300 IdsIndex::const_iterator it = kernel_->delete_journals_->begin();
301 while (it != kernel_->delete_journals_->end()) {
302 int64 handle = (*it)->ref(META_HANDLE);
303 if (to_purge.count(handle)) {
304 delete *it;
305 kernel_->delete_journals_->erase(it++);
306 } else {
307 ++it;
308 }
309 }
310 kernel_->delete_journals_to_purge_->insert(to_purge.begin(), to_purge.end());
311 }
239 312
240 EntryKernel* Directory::GetEntryById(const Id& id) { 313 EntryKernel* Directory::GetEntryById(const Id& id) {
241 ScopedKernelLock lock(this); 314 ScopedKernelLock lock(this);
242 return GetEntryById(id, &lock); 315 return GetEntryById(id, &lock);
243 } 316 }
244 317
245 EntryKernel* Directory::GetEntryById(const Id& id, 318 EntryKernel* Directory::GetEntryById(const Id& id,
246 ScopedKernelLock* const lock) { 319 ScopedKernelLock* const lock) {
247 DCHECK(kernel_); 320 DCHECK(kernel_);
248 // Find it in the in memory ID index. 321 // Find it in the in memory ID index.
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after
459 // Deep copy dirty entries from kernel_->metahandles_index into snapshot and 532 // Deep copy dirty entries from kernel_->metahandles_index into snapshot and
460 // clear dirty flags. 533 // clear dirty flags.
461 for (MetahandleSet::const_iterator i = kernel_->dirty_metahandles->begin(); 534 for (MetahandleSet::const_iterator i = kernel_->dirty_metahandles->begin();
462 i != kernel_->dirty_metahandles->end(); ++i) { 535 i != kernel_->dirty_metahandles->end(); ++i) {
463 EntryKernel* entry = GetEntryByHandle(*i, &lock); 536 EntryKernel* entry = GetEntryByHandle(*i, &lock);
464 if (!entry) 537 if (!entry)
465 continue; 538 continue;
466 // Skip over false positives; it happens relatively infrequently. 539 // Skip over false positives; it happens relatively infrequently.
467 if (!entry->is_dirty()) 540 if (!entry->is_dirty())
468 continue; 541 continue;
469 snapshot->dirty_metas.insert(snapshot->dirty_metas.end(), *entry); 542 snapshot->dirty_metas.insert(snapshot->dirty_metas.end(),
543 new EntryKernel(*entry));
470 DCHECK_EQ(1U, kernel_->dirty_metahandles->count(*i)); 544 DCHECK_EQ(1U, kernel_->dirty_metahandles->count(*i));
471 // We don't bother removing from the index here as we blow the entire thing 545 // We don't bother removing from the index here as we blow the entire thing
472 // in a moment, and it unnecessarily complicates iteration. 546 // in a moment, and it unnecessarily complicates iteration.
473 entry->clear_dirty(NULL); 547 entry->clear_dirty(NULL);
474 } 548 }
475 ClearDirtyMetahandles(); 549 ClearDirtyMetahandles();
476 550
477 // Set purged handles. 551 // Set purged handles.
478 DCHECK(snapshot->metahandles_to_purge.empty()); 552 DCHECK(snapshot->metahandles_to_purge.empty());
479 snapshot->metahandles_to_purge.swap(*(kernel_->metahandles_to_purge)); 553 snapshot->metahandles_to_purge.swap(*(kernel_->metahandles_to_purge));
480 554
481 // Fill kernel_info_status and kernel_info. 555 // Fill kernel_info_status and kernel_info.
482 snapshot->kernel_info = kernel_->persisted_info; 556 snapshot->kernel_info = kernel_->persisted_info;
483 // To avoid duplicates when the process crashes, we record the next_id to be 557 // To avoid duplicates when the process crashes, we record the next_id to be
484 // greater magnitude than could possibly be reached before the next save 558 // greater magnitude than could possibly be reached before the next save
485 // changes. In other words, it's effectively impossible for the user to 559 // changes. In other words, it's effectively impossible for the user to
486 // generate 65536 new bookmarks in 3 seconds. 560 // generate 65536 new bookmarks in 3 seconds.
487 snapshot->kernel_info.next_id -= 65536; 561 snapshot->kernel_info.next_id -= 65536;
488 snapshot->kernel_info_status = kernel_->info_status; 562 snapshot->kernel_info_status = kernel_->info_status;
489 // This one we reset on failure. 563 // This one we reset on failure.
490 kernel_->info_status = KERNEL_SHARE_INFO_VALID; 564 kernel_->info_status = KERNEL_SHARE_INFO_VALID;
565
566 // Move passive delete journals to snapshot. Will copy back if snapshot fails
567 // to save.
568 MetahandlesIndex::const_iterator it = kernel_->delete_journals_->begin();
569 while (it != kernel_->delete_journals_->end()) {
570 if (passive_delete_journal_types_.Has((*it)->GetServerModelType())) {
571 snapshot->delete_journals.insert(*it);
572 kernel_->delete_journals_->erase(it++);
573 } else {
574 ++it;
575 }
576 }
577 snapshot->delete_journals_to_purge.swap(
578 *kernel_->delete_journals_to_purge_);
491 } 579 }
492 580
493 bool Directory::SaveChanges() { 581 bool Directory::SaveChanges() {
494 bool success = false; 582 bool success = false;
495 583
496 base::AutoLock scoped_lock(kernel_->save_changes_mutex); 584 base::AutoLock scoped_lock(kernel_->save_changes_mutex);
497 585
498 // Snapshot and save. 586 // Snapshot and save.
499 SaveChangesSnapshot snapshot; 587 SaveChangesSnapshot snapshot;
500 TakeSnapshotForSaveChanges(&snapshot); 588 TakeSnapshotForSaveChanges(&snapshot);
(...skipping 10 matching lines...) Expand all
511 bool Directory::VacuumAfterSaveChanges(const SaveChangesSnapshot& snapshot) { 599 bool Directory::VacuumAfterSaveChanges(const SaveChangesSnapshot& snapshot) {
512 if (snapshot.dirty_metas.empty()) 600 if (snapshot.dirty_metas.empty())
513 return true; 601 return true;
514 602
515 // Need a write transaction as we are about to permanently purge entries. 603 // Need a write transaction as we are about to permanently purge entries.
516 WriteTransaction trans(FROM_HERE, VACUUM_AFTER_SAVE, this); 604 WriteTransaction trans(FROM_HERE, VACUUM_AFTER_SAVE, this);
517 ScopedKernelLock lock(this); 605 ScopedKernelLock lock(this);
518 // Now drop everything we can out of memory. 606 // Now drop everything we can out of memory.
519 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); 607 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
520 i != snapshot.dirty_metas.end(); ++i) { 608 i != snapshot.dirty_metas.end(); ++i) {
521 kernel_->needle.put(META_HANDLE, i->ref(META_HANDLE)); 609 kernel_->needle.put(META_HANDLE, (*i)->ref(META_HANDLE));
522 MetahandlesIndex::iterator found = 610 MetahandlesIndex::iterator found =
523 kernel_->metahandles_index->find(&kernel_->needle); 611 kernel_->metahandles_index->find(&kernel_->needle);
524 EntryKernel* entry = (found == kernel_->metahandles_index->end() ? 612 EntryKernel* entry = (found == kernel_->metahandles_index->end() ?
525 NULL : *found); 613 NULL : *found);
526 if (entry && SafeToPurgeFromMemory(&trans, entry)) { 614 if (entry && SafeToPurgeFromMemory(&trans, entry)) {
527 // We now drop deleted metahandles that are up to date on both the client 615 // We now drop deleted metahandles that are up to date on both the client
528 // and the server. 616 // and the server.
529 size_t num_erased = 0; 617 size_t num_erased = 0;
530 num_erased = kernel_->ids_index->erase(entry); 618 num_erased = kernel_->ids_index->erase(entry);
531 DCHECK_EQ(1u, num_erased); 619 DCHECK_EQ(1u, num_erased);
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 ScopedKernelLock lock(this); 697 ScopedKernelLock lock(this);
610 kernel_->info_status = KERNEL_SHARE_INFO_DIRTY; 698 kernel_->info_status = KERNEL_SHARE_INFO_DIRTY;
611 699
612 // Because we optimistically cleared the dirty bit on the real entries when 700 // Because we optimistically cleared the dirty bit on the real entries when
613 // taking the snapshot, we must restore it on failure. Not doing this could 701 // taking the snapshot, we must restore it on failure. Not doing this could
614 // cause lost data, if no other changes are made to the in-memory entries 702 // cause lost data, if no other changes are made to the in-memory entries
615 // that would cause the dirty bit to get set again. Setting the bit ensures 703 // that would cause the dirty bit to get set again. Setting the bit ensures
616 // that SaveChanges will at least try again later. 704 // that SaveChanges will at least try again later.
617 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin(); 705 for (EntryKernelSet::const_iterator i = snapshot.dirty_metas.begin();
618 i != snapshot.dirty_metas.end(); ++i) { 706 i != snapshot.dirty_metas.end(); ++i) {
619 kernel_->needle.put(META_HANDLE, i->ref(META_HANDLE)); 707 kernel_->needle.put(META_HANDLE, (*i)->ref(META_HANDLE));
620 MetahandlesIndex::iterator found = 708 MetahandlesIndex::iterator found =
621 kernel_->metahandles_index->find(&kernel_->needle); 709 kernel_->metahandles_index->find(&kernel_->needle);
622 if (found != kernel_->metahandles_index->end()) { 710 if (found != kernel_->metahandles_index->end()) {
623 (*found)->mark_dirty(kernel_->dirty_metahandles); 711 (*found)->mark_dirty(kernel_->dirty_metahandles);
624 } 712 }
625 } 713 }
626 714
627 kernel_->metahandles_to_purge->insert(snapshot.metahandles_to_purge.begin(), 715 kernel_->metahandles_to_purge->insert(snapshot.metahandles_to_purge.begin(),
628 snapshot.metahandles_to_purge.end()); 716 snapshot.metahandles_to_purge.end());
717
718 // Restore delete journals.
719 for (EntryKernelSet::const_iterator i = snapshot.delete_journals.begin();
720 i != snapshot.delete_journals.end(); ++i) {
721 kernel_->needle.put(ID, (*i)->ref(ID));
722 if (kernel_->delete_journals_->find(&kernel_->needle) ==
723 kernel_->delete_journals_->end()) {
724 kernel_->delete_journals_->insert(new EntryKernel(**i));
725 }
726 }
727 kernel_->delete_journals_to_purge_->insert(
728 snapshot.delete_journals_to_purge.begin(),
729 snapshot.delete_journals_to_purge.end());
730
629 } 731 }
630 732
631 void Directory::GetDownloadProgress( 733 void Directory::GetDownloadProgress(
632 ModelType model_type, 734 ModelType model_type,
633 sync_pb::DataTypeProgressMarker* value_out) const { 735 sync_pb::DataTypeProgressMarker* value_out) const {
634 ScopedKernelLock lock(this); 736 ScopedKernelLock lock(this);
635 return value_out->CopyFrom( 737 return value_out->CopyFrom(
636 kernel_->persisted_info.download_progress[model_type]); 738 kernel_->persisted_info.download_progress[model_type]);
637 } 739 }
638 740
(...skipping 622 matching lines...) Expand 10 before | Expand all | Expand 10 after
1261 // ordering. 1363 // ordering.
1262 if (entry->ref(PREV_ID).IsRoot() || 1364 if (entry->ref(PREV_ID).IsRoot() ||
1263 entry->ref(PREV_ID) != entry->ref(NEXT_ID)) { 1365 entry->ref(PREV_ID) != entry->ref(NEXT_ID)) {
1264 return entry; 1366 return entry;
1265 } 1367 }
1266 } 1368 }
1267 // There were no children in the linked list. 1369 // There were no children in the linked list.
1268 return NULL; 1370 return NULL;
1269 } 1371 }
1270 1372
1373 /* static */
1374 bool Directory::IsDeleteJournalEnabled(ModelType type) {
1375 switch (type) {
1376 case BOOKMARKS:
1377 return true;
1378 default:
1379 return false;
1380 }
1381 }
1382
1271 ScopedKernelLock::ScopedKernelLock(const Directory* dir) 1383 ScopedKernelLock::ScopedKernelLock(const Directory* dir)
1272 : scoped_lock_(dir->kernel_->mutex), dir_(const_cast<Directory*>(dir)) { 1384 : scoped_lock_(dir->kernel_->mutex), dir_(const_cast<Directory*>(dir)) {
1273 } 1385 }
1274 1386
1275 } // namespace syncable 1387 } // namespace syncable
1276 } // namespace syncer 1388 } // namespace syncer
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698