syncapi.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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. 4 5#include "chrome/browser/sync/engine/syncapi.h" 6 7#include <bitset> 8#include <iomanip> 9#include <list> 10#include <string> 11#include <vector> 12 13#include "base/base64.h" 14#include "base/lock.h" 15#include "base/logging.h" 16#include "base/message_loop.h" 17#include "base/platform_thread.h" 18#include "base/scoped_ptr.h" 19#include "base/sha1.h" 20#include "base/string_util.h" 21#include "base/task.h" 22#include "base/utf_string_conversions.h" 23#include "chrome/browser/browser_thread.h" 24#include "chrome/browser/sync/sync_constants.h" 25#include "chrome/browser/sync/engine/all_status.h" 26#include "chrome/browser/sync/engine/change_reorder_buffer.h" 27#include "chrome/browser/sync/engine/model_safe_worker.h" 28#include "chrome/browser/sync/engine/net/server_connection_manager.h" 29#include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" 30#include "chrome/browser/sync/engine/syncer.h" 31#include "chrome/browser/sync/engine/syncer_thread.h" 32#include "chrome/browser/sync/notifier/server_notifier_thread.h" 33#include "chrome/browser/sync/notifier/state_writer.h" 34#include "chrome/browser/sync/protocol/app_specifics.pb.h" 35#include "chrome/browser/sync/protocol/autofill_specifics.pb.h" 36#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" 37#include "chrome/browser/sync/protocol/extension_specifics.pb.h" 38#include "chrome/browser/sync/protocol/nigori_specifics.pb.h" 39#include "chrome/browser/sync/protocol/preference_specifics.pb.h" 40#include "chrome/browser/sync/protocol/session_specifics.pb.h" 41#include "chrome/browser/sync/protocol/service_constants.h" 42#include "chrome/browser/sync/protocol/sync.pb.h" 43#include "chrome/browser/sync/protocol/theme_specifics.pb.h" 44#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" 45#include "chrome/browser/sync/sessions/sync_session_context.h" 46#include "chrome/browser/sync/syncable/autofill_migration.h" 47#include "chrome/browser/sync/syncable/directory_manager.h" 48#include "chrome/browser/sync/syncable/syncable.h" 49#include "chrome/browser/sync/util/crypto_helpers.h" 50#include "chrome/common/chrome_switches.h" 51#include "chrome/common/deprecated/event_sys.h" 52#include "chrome/common/net/gaia/gaia_authenticator.h" 53#include "jingle/notifier/listener/mediator_thread_impl.h" 54#include "jingle/notifier/listener/notification_constants.h" 55#include "jingle/notifier/listener/talk_mediator.h" 56#include "jingle/notifier/listener/talk_mediator_impl.h" 57#include "net/base/network_change_notifier.h" 58 59using browser_sync::AllStatus; 60using browser_sync::Cryptographer; 61using browser_sync::KeyParams; 62using browser_sync::ModelSafeRoutingInfo; 63using browser_sync::ModelSafeWorker; 64using browser_sync::ModelSafeWorkerRegistrar; 65using browser_sync::ServerConnectionEvent; 66using browser_sync::SyncEngineEvent; 67using browser_sync::SyncEngineEventListener; 68using browser_sync::Syncer; 69using browser_sync::SyncerThread; 70using browser_sync::kNigoriTag; 71using browser_sync::sessions::SyncSessionContext; 72using notifier::TalkMediator; 73using notifier::TalkMediatorImpl; 74using std::list; 75using std::hex; 76using std::string; 77using std::vector; 78using syncable::Directory; 79using syncable::DirectoryManager; 80using syncable::Entry; 81using syncable::SPECIFICS; 82using sync_pb::AutofillProfileSpecifics; 83 84typedef GoogleServiceAuthError AuthError; 85 86static const int kThreadExitTimeoutMsec = 60000; 87static const int kSSLPort = 443; 88 89#if defined(OS_CHROMEOS) 90static const int kChromeOSNetworkChangeReactionDelayHackMsec = 5000; 91#endif // OS_CHROMEOS 92 93// We manage the lifetime of sync_api::SyncManager::SyncInternal ourselves. 94DISABLE_RUNNABLE_METHOD_REFCOUNT(sync_api::SyncManager::SyncInternal); 95 96namespace sync_api { 97 98static const FilePath::CharType kBookmarkSyncUserSettingsDatabase[] = 99 FILE_PATH_LITERAL("BookmarkSyncSettings.sqlite3"); 100static const char kDefaultNameForNewNodes[] = " "; 101 102// The list of names which are reserved for use by the server. 103static const char* kForbiddenServerNames[] = { "", ".", ".." }; 104 105////////////////////////////////////////////////////////////////////////// 106// Static helper functions. 107 108// Helper function to look up the int64 metahandle of an object given the ID 109// string. 110static int64 IdToMetahandle(syncable::BaseTransaction* trans, 111 const syncable::Id& id) { 112 syncable::Entry entry(trans, syncable::GET_BY_ID, id); 113 if (!entry.good()) 114 return kInvalidId; 115 return entry.Get(syncable::META_HANDLE); 116} 117 118// Checks whether |name| is a server-illegal name followed by zero or more space 119// characters. The three server-illegal names are the empty string, dot, and 120// dot-dot. Very long names (>255 bytes in UTF-8 Normalization Form C) are 121// also illegal, but are not considered here. 122static bool IsNameServerIllegalAfterTrimming(const std::string& name) { 123 size_t untrimmed_count = name.find_last_not_of(' ') + 1; 124 for (size_t i = 0; i < arraysize(kForbiddenServerNames); ++i) { 125 if (name.compare(0, untrimmed_count, kForbiddenServerNames[i]) == 0) 126 return true; 127 } 128 return false; 129} 130 131static bool EndsWithSpace(const std::string& string) { 132 return !string.empty() && *string.rbegin() == ' '; 133} 134 135// When taking a name from the syncapi, append a space if it matches the 136// pattern of a server-illegal name followed by zero or more spaces. 137static void SyncAPINameToServerName(const std::wstring& sync_api_name, 138 std::string* out) { 139 *out = WideToUTF8(sync_api_name); 140 if (IsNameServerIllegalAfterTrimming(*out)) 141 out->append(" "); 142} 143 144// In the reverse direction, if a server name matches the pattern of a 145// server-illegal name followed by one or more spaces, remove the trailing 146// space. 147static void ServerNameToSyncAPIName(const std::string& server_name, 148 std::wstring* out) { 149 int length_to_copy = server_name.length(); 150 if (IsNameServerIllegalAfterTrimming(server_name) && 151 EndsWithSpace(server_name)) 152 --length_to_copy; 153 if (!UTF8ToWide(server_name.c_str(), length_to_copy, out)) { 154 NOTREACHED() << "Could not convert server name from UTF8 to wide"; 155 } 156} 157 158UserShare::UserShare() {} 159 160UserShare::~UserShare() {} 161 162//////////////////////////////////// 163// BaseNode member definitions. 164 165BaseNode::BaseNode() {} 166 167BaseNode::~BaseNode() {} 168 169std::string BaseNode::GenerateSyncableHash( 170 syncable::ModelType model_type, const std::string& client_tag) { 171 // blank PB with just the extension in it has termination symbol, 172 // handy for delimiter 173 sync_pb::EntitySpecifics serialized_type; 174 syncable::AddDefaultExtensionValue(model_type, &serialized_type); 175 std::string hash_input; 176 serialized_type.AppendToString(&hash_input); 177 hash_input.append(client_tag); 178 179 std::string encode_output; 180 CHECK(base::Base64Encode(base::SHA1HashString(hash_input), &encode_output)); 181 return encode_output; 182} 183 184sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics( 185 const sync_pb::EntitySpecifics& specifics, Cryptographer* crypto) { 186 if (!specifics.HasExtension(sync_pb::password)) 187 return NULL; 188 const sync_pb::EncryptedData& encrypted = 189 specifics.GetExtension(sync_pb::password).encrypted(); 190 scoped_ptr<sync_pb::PasswordSpecificsData> data( 191 new sync_pb::PasswordSpecificsData); 192 if (!crypto->Decrypt(encrypted, data.get())) 193 return NULL; 194 return data.release(); 195} 196 197bool BaseNode::DecryptIfNecessary(Entry* entry) { 198 if (GetIsFolder()) return true; // Ignore the top-level password folder. 199 const sync_pb::EntitySpecifics& specifics = 200 entry->Get(syncable::SPECIFICS); 201 if (specifics.HasExtension(sync_pb::password)) { 202 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics( 203 specifics, GetTransaction()->GetCryptographer())); 204 if (!data.get()) 205 return false; 206 password_data_.swap(data); 207 } 208 return true; 209} 210 211int64 BaseNode::GetParentId() const { 212 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), 213 GetEntry()->Get(syncable::PARENT_ID)); 214} 215 216int64 BaseNode::GetId() const { 217 return GetEntry()->Get(syncable::META_HANDLE); 218} 219 220bool BaseNode::GetIsFolder() const { 221 return GetEntry()->Get(syncable::IS_DIR); 222} 223 224std::wstring BaseNode::GetTitle() const { 225 std::wstring result; 226 ServerNameToSyncAPIName(GetEntry()->Get(syncable::NON_UNIQUE_NAME), &result); 227 return result; 228} 229 230GURL BaseNode::GetURL() const { 231 return GURL(GetBookmarkSpecifics().url()); 232} 233 234int64 BaseNode::GetPredecessorId() const { 235 syncable::Id id_string = GetEntry()->Get(syncable::PREV_ID); 236 if (id_string.IsRoot()) 237 return kInvalidId; 238 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 239} 240 241int64 BaseNode::GetSuccessorId() const { 242 syncable::Id id_string = GetEntry()->Get(syncable::NEXT_ID); 243 if (id_string.IsRoot()) 244 return kInvalidId; 245 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 246} 247 248int64 BaseNode::GetFirstChildId() const { 249 syncable::Directory* dir = GetTransaction()->GetLookup(); 250 syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans(); 251 syncable::Id id_string = 252 dir->GetFirstChildId(trans, GetEntry()->Get(syncable::ID)); 253 if (id_string.IsRoot()) 254 return kInvalidId; 255 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 256} 257 258void BaseNode::GetFaviconBytes(std::vector<unsigned char>* output) const { 259 if (!output) 260 return; 261 const std::string& favicon = GetBookmarkSpecifics().favicon(); 262 output->assign(reinterpret_cast<const unsigned char*>(favicon.data()), 263 reinterpret_cast<const unsigned char*>(favicon.data() + 264 favicon.length())); 265} 266 267int64 BaseNode::GetExternalId() const { 268 return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID); 269} 270 271const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const { 272 DCHECK(GetModelType() == syncable::APPS); 273 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::app); 274} 275 276const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const { 277 DCHECK(GetModelType() == syncable::AUTOFILL); 278 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill); 279} 280 281const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const { 282 DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE); 283 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill_profile); 284} 285 286const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const { 287 DCHECK(GetModelType() == syncable::BOOKMARKS); 288 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::bookmark); 289} 290 291const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const { 292 DCHECK(GetModelType() == syncable::NIGORI); 293 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::nigori); 294} 295 296const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { 297 DCHECK(GetModelType() == syncable::PASSWORDS); 298 DCHECK(password_data_.get()); 299 return *password_data_; 300} 301 302const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const { 303 DCHECK(GetModelType() == syncable::PREFERENCES); 304 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference); 305} 306 307const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { 308 DCHECK(GetModelType() == syncable::THEMES); 309 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::theme); 310} 311 312const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { 313 DCHECK(GetModelType() == syncable::TYPED_URLS); 314 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url); 315} 316 317const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const { 318 DCHECK(GetModelType() == syncable::EXTENSIONS); 319 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::extension); 320} 321 322const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const { 323 DCHECK(GetModelType() == syncable::SESSIONS); 324 return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::session); 325} 326 327syncable::ModelType BaseNode::GetModelType() const { 328 return GetEntry()->GetModelType(); 329} 330 331//////////////////////////////////// 332// WriteNode member definitions 333void WriteNode::SetIsFolder(bool folder) { 334 if (entry_->Get(syncable::IS_DIR) == folder) 335 return; // Skip redundant changes. 336 337 entry_->Put(syncable::IS_DIR, folder); 338 MarkForSyncing(); 339} 340 341void WriteNode::SetTitle(const std::wstring& title) { 342 std::string server_legal_name; 343 SyncAPINameToServerName(title, &server_legal_name); 344 345 string old_name = entry_->Get(syncable::NON_UNIQUE_NAME); 346 347 if (server_legal_name == old_name) 348 return; // Skip redundant changes. 349 350 entry_->Put(syncable::NON_UNIQUE_NAME, server_legal_name); 351 MarkForSyncing(); 352} 353 354void WriteNode::SetURL(const GURL& url) { 355 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); 356 new_value.set_url(url.spec()); 357 SetBookmarkSpecifics(new_value); 358} 359 360void WriteNode::SetAppSpecifics( 361 const sync_pb::AppSpecifics& new_value) { 362 DCHECK(GetModelType() == syncable::APPS); 363 PutAppSpecificsAndMarkForSyncing(new_value); 364} 365 366void WriteNode::SetAutofillSpecifics( 367 const sync_pb::AutofillSpecifics& new_value) { 368 DCHECK(GetModelType() == syncable::AUTOFILL); 369 PutAutofillSpecificsAndMarkForSyncing(new_value); 370} 371 372void WriteNode::PutAutofillSpecificsAndMarkForSyncing( 373 const sync_pb::AutofillSpecifics& new_value) { 374 sync_pb::EntitySpecifics entity_specifics; 375 entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value); 376 PutSpecificsAndMarkForSyncing(entity_specifics); 377} 378 379void WriteNode::SetAutofillProfileSpecifics( 380 const sync_pb::AutofillProfileSpecifics& new_value) { 381 DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE); 382 PutAutofillProfileSpecificsAndMarkForSyncing(new_value); 383} 384 385void WriteNode::PutAutofillProfileSpecificsAndMarkForSyncing( 386 const sync_pb::AutofillProfileSpecifics& new_value) { 387 sync_pb::EntitySpecifics entity_specifics; 388 entity_specifics.MutableExtension(sync_pb::autofill_profile)->CopyFrom( 389 new_value); 390 PutSpecificsAndMarkForSyncing(entity_specifics); 391} 392 393void WriteNode::SetBookmarkSpecifics( 394 const sync_pb::BookmarkSpecifics& new_value) { 395 DCHECK(GetModelType() == syncable::BOOKMARKS); 396 PutBookmarkSpecificsAndMarkForSyncing(new_value); 397} 398 399void WriteNode::PutBookmarkSpecificsAndMarkForSyncing( 400 const sync_pb::BookmarkSpecifics& new_value) { 401 sync_pb::EntitySpecifics entity_specifics; 402 entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value); 403 PutSpecificsAndMarkForSyncing(entity_specifics); 404} 405 406void WriteNode::SetNigoriSpecifics( 407 const sync_pb::NigoriSpecifics& new_value) { 408 DCHECK(GetModelType() == syncable::NIGORI); 409 PutNigoriSpecificsAndMarkForSyncing(new_value); 410} 411 412void WriteNode::PutNigoriSpecificsAndMarkForSyncing( 413 const sync_pb::NigoriSpecifics& new_value) { 414 sync_pb::EntitySpecifics entity_specifics; 415 entity_specifics.MutableExtension(sync_pb::nigori)->CopyFrom(new_value); 416 PutSpecificsAndMarkForSyncing(entity_specifics); 417} 418 419void WriteNode::SetPasswordSpecifics( 420 const sync_pb::PasswordSpecificsData& data) { 421 DCHECK(GetModelType() == syncable::PASSWORDS); 422 423 sync_pb::PasswordSpecifics new_value; 424 if (!GetTransaction()->GetCryptographer()->Encrypt( 425 data, 426 new_value.mutable_encrypted())) { 427 NOTREACHED(); 428 } 429 430 PutPasswordSpecificsAndMarkForSyncing(new_value); 431} 432 433void WriteNode::SetPreferenceSpecifics( 434 const sync_pb::PreferenceSpecifics& new_value) { 435 DCHECK(GetModelType() == syncable::PREFERENCES); 436 PutPreferenceSpecificsAndMarkForSyncing(new_value); 437} 438 439void WriteNode::SetThemeSpecifics( 440 const sync_pb::ThemeSpecifics& new_value) { 441 DCHECK(GetModelType() == syncable::THEMES); 442 PutThemeSpecificsAndMarkForSyncing(new_value); 443} 444 445void WriteNode::SetSessionSpecifics( 446 const sync_pb::SessionSpecifics& new_value) { 447 DCHECK(GetModelType() == syncable::SESSIONS); 448 PutSessionSpecificsAndMarkForSyncing(new_value); 449} 450 451 452void WriteNode::PutPasswordSpecificsAndMarkForSyncing( 453 const sync_pb::PasswordSpecifics& new_value) { 454 sync_pb::EntitySpecifics entity_specifics; 455 entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value); 456 PutSpecificsAndMarkForSyncing(entity_specifics); 457} 458 459void WriteNode::PutPreferenceSpecificsAndMarkForSyncing( 460 const sync_pb::PreferenceSpecifics& new_value) { 461 sync_pb::EntitySpecifics entity_specifics; 462 entity_specifics.MutableExtension(sync_pb::preference)->CopyFrom(new_value); 463 PutSpecificsAndMarkForSyncing(entity_specifics); 464} 465 466void WriteNode::SetTypedUrlSpecifics( 467 const sync_pb::TypedUrlSpecifics& new_value) { 468 DCHECK(GetModelType() == syncable::TYPED_URLS); 469 PutTypedUrlSpecificsAndMarkForSyncing(new_value); 470} 471 472void WriteNode::SetExtensionSpecifics( 473 const sync_pb::ExtensionSpecifics& new_value) { 474 DCHECK(GetModelType() == syncable::EXTENSIONS); 475 PutExtensionSpecificsAndMarkForSyncing(new_value); 476} 477 478void WriteNode::PutAppSpecificsAndMarkForSyncing( 479 const sync_pb::AppSpecifics& new_value) { 480 sync_pb::EntitySpecifics entity_specifics; 481 entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value); 482 PutSpecificsAndMarkForSyncing(entity_specifics); 483} 484 485void WriteNode::PutThemeSpecificsAndMarkForSyncing( 486 const sync_pb::ThemeSpecifics& new_value) { 487 sync_pb::EntitySpecifics entity_specifics; 488 entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value); 489 PutSpecificsAndMarkForSyncing(entity_specifics); 490} 491 492void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing( 493 const sync_pb::TypedUrlSpecifics& new_value) { 494 sync_pb::EntitySpecifics entity_specifics; 495 entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value); 496 PutSpecificsAndMarkForSyncing(entity_specifics); 497} 498 499void WriteNode::PutExtensionSpecificsAndMarkForSyncing( 500 const sync_pb::ExtensionSpecifics& new_value) { 501 sync_pb::EntitySpecifics entity_specifics; 502 entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value); 503 PutSpecificsAndMarkForSyncing(entity_specifics); 504} 505 506 507void WriteNode::PutSessionSpecificsAndMarkForSyncing( 508 const sync_pb::SessionSpecifics& new_value) { 509 sync_pb::EntitySpecifics entity_specifics; 510 entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value); 511 PutSpecificsAndMarkForSyncing(entity_specifics); 512} 513 514 515void WriteNode::PutSpecificsAndMarkForSyncing( 516 const sync_pb::EntitySpecifics& specifics) { 517 // Skip redundant changes. 518 if (specifics.SerializeAsString() == 519 entry_->Get(SPECIFICS).SerializeAsString()) { 520 return; 521 } 522 entry_->Put(SPECIFICS, specifics); 523 MarkForSyncing(); 524} 525 526void WriteNode::SetExternalId(int64 id) { 527 if (GetExternalId() != id) 528 entry_->Put(syncable::LOCAL_EXTERNAL_ID, id); 529} 530 531WriteNode::WriteNode(WriteTransaction* transaction) 532 : entry_(NULL), transaction_(transaction) { 533 DCHECK(transaction); 534} 535 536WriteNode::~WriteNode() { 537 delete entry_; 538} 539 540// Find an existing node matching the ID |id|, and bind this WriteNode to it. 541// Return true on success. 542bool WriteNode::InitByIdLookup(int64 id) { 543 DCHECK(!entry_) << "Init called twice"; 544 DCHECK_NE(id, kInvalidId); 545 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 546 syncable::GET_BY_HANDLE, id); 547 return (entry_->good() && !entry_->Get(syncable::IS_DEL) && 548 DecryptIfNecessary(entry_)); 549} 550 551// Find a node by client tag, and bind this WriteNode to it. 552// Return true if the write node was found, and was not deleted. 553// Undeleting a deleted node is possible by ClientTag. 554bool WriteNode::InitByClientTagLookup(syncable::ModelType model_type, 555 const std::string& tag) { 556 DCHECK(!entry_) << "Init called twice"; 557 if (tag.empty()) 558 return false; 559 560 const std::string hash = GenerateSyncableHash(model_type, tag); 561 562 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 563 syncable::GET_BY_CLIENT_TAG, hash); 564 return (entry_->good() && !entry_->Get(syncable::IS_DEL) && 565 DecryptIfNecessary(entry_)); 566} 567 568bool WriteNode::InitByTagLookup(const std::string& tag) { 569 DCHECK(!entry_) << "Init called twice"; 570 if (tag.empty()) 571 return false; 572 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 573 syncable::GET_BY_SERVER_TAG, tag); 574 if (!entry_->good()) 575 return false; 576 if (entry_->Get(syncable::IS_DEL)) 577 return false; 578 syncable::ModelType model_type = GetModelType(); 579 DCHECK(model_type == syncable::NIGORI); 580 return true; 581} 582 583void WriteNode::PutModelType(syncable::ModelType model_type) { 584 // Set an empty specifics of the appropriate datatype. The presence 585 // of the specific extension will identify the model type. 586 DCHECK(GetModelType() == model_type || 587 GetModelType() == syncable::UNSPECIFIED); // Immutable once set. 588 589 sync_pb::EntitySpecifics specifics; 590 syncable::AddDefaultExtensionValue(model_type, &specifics); 591 PutSpecificsAndMarkForSyncing(specifics); 592 DCHECK(GetModelType() == model_type); 593} 594 595// Create a new node with default properties, and bind this WriteNode to it. 596// Return true on success. 597bool WriteNode::InitByCreation(syncable::ModelType model_type, 598 const BaseNode& parent, 599 const BaseNode* predecessor) { 600 DCHECK(!entry_) << "Init called twice"; 601 // |predecessor| must be a child of |parent| or NULL. 602 if (predecessor && predecessor->GetParentId() != parent.GetId()) { 603 DCHECK(false); 604 return false; 605 } 606 607 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); 608 609 // Start out with a dummy name. We expect 610 // the caller to set a meaningful name after creation. 611 string dummy(kDefaultNameForNewNodes); 612 613 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 614 syncable::CREATE, parent_id, dummy); 615 616 if (!entry_->good()) 617 return false; 618 619 // Entries are untitled folders by default. 620 entry_->Put(syncable::IS_DIR, true); 621 622 PutModelType(model_type); 623 624 // Now set the predecessor, which sets IS_UNSYNCED as necessary. 625 PutPredecessor(predecessor); 626 627 return true; 628} 629 630// Create a new node with default properties and a client defined unique tag, 631// and bind this WriteNode to it. 632// Return true on success. If the tag exists in the database, then 633// we will attempt to undelete the node. 634// TODO(chron): Code datatype into hash tag. 635// TODO(chron): Is model type ever lost? 636bool WriteNode::InitUniqueByCreation(syncable::ModelType model_type, 637 const BaseNode& parent, 638 const std::string& tag) { 639 DCHECK(!entry_) << "Init called twice"; 640 641 const std::string hash = GenerateSyncableHash(model_type, tag); 642 643 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); 644 645 // Start out with a dummy name. We expect 646 // the caller to set a meaningful name after creation. 647 string dummy(kDefaultNameForNewNodes); 648 649 // Check if we have this locally and need to undelete it. 650 scoped_ptr<syncable::MutableEntry> existing_entry( 651 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 652 syncable::GET_BY_CLIENT_TAG, hash)); 653 654 if (existing_entry->good()) { 655 if (existing_entry->Get(syncable::IS_DEL)) { 656 // Rules for undelete: 657 // BASE_VERSION: Must keep the same. 658 // ID: Essential to keep the same. 659 // META_HANDLE: Must be the same, so we can't "split" the entry. 660 // IS_DEL: Must be set to false, will cause reindexing. 661 // This one is weird because IS_DEL is true for "update only" 662 // items. It should be OK to undelete an update only. 663 // MTIME/CTIME: Seems reasonable to just leave them alone. 664 // IS_UNSYNCED: Must set this to true or face database insurrection. 665 // We do this below this block. 666 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION 667 // to SERVER_VERSION. We keep it the same here. 668 // IS_DIR: We'll leave it the same. 669 // SPECIFICS: Reset it. 670 671 existing_entry->Put(syncable::IS_DEL, false); 672 673 // Client tags are immutable and must be paired with the ID. 674 // If a server update comes down with an ID and client tag combo, 675 // and it already exists, always overwrite it and store only one copy. 676 // We have to undelete entries because we can't disassociate IDs from 677 // tags and updates. 678 679 existing_entry->Put(syncable::NON_UNIQUE_NAME, dummy); 680 existing_entry->Put(syncable::PARENT_ID, parent_id); 681 entry_ = existing_entry.release(); 682 } else { 683 return false; 684 } 685 } else { 686 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), 687 syncable::CREATE, parent_id, dummy); 688 if (!entry_->good()) { 689 return false; 690 } 691 692 // Only set IS_DIR for new entries. Don't bitflip undeleted ones. 693 entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash); 694 } 695 696 // We don't support directory and tag combinations. 697 entry_->Put(syncable::IS_DIR, false); 698 699 // Will clear specifics data. 700 PutModelType(model_type); 701 702 // Now set the predecessor, which sets IS_UNSYNCED as necessary. 703 PutPredecessor(NULL); 704 705 return true; 706} 707 708bool WriteNode::SetPosition(const BaseNode& new_parent, 709 const BaseNode* predecessor) { 710 // |predecessor| must be a child of |new_parent| or NULL. 711 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { 712 DCHECK(false); 713 return false; 714 } 715 716 syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID); 717 718 // Filter out redundant changes if both the parent and the predecessor match. 719 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) { 720 const syncable::Id& old = entry_->Get(syncable::PREV_ID); 721 if ((!predecessor && old.IsRoot()) || 722 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) { 723 return true; 724 } 725 } 726 727 // Atomically change the parent. This will fail if it would 728 // introduce a cycle in the hierarchy. 729 if (!entry_->Put(syncable::PARENT_ID, new_parent_id)) 730 return false; 731 732 // Now set the predecessor, which sets IS_UNSYNCED as necessary. 733 PutPredecessor(predecessor); 734 735 return true; 736} 737 738const syncable::Entry* WriteNode::GetEntry() const { 739 return entry_; 740} 741 742const BaseTransaction* WriteNode::GetTransaction() const { 743 return transaction_; 744} 745 746void WriteNode::Remove() { 747 entry_->Put(syncable::IS_DEL, true); 748 MarkForSyncing(); 749} 750 751void WriteNode::PutPredecessor(const BaseNode* predecessor) { 752 syncable::Id predecessor_id = predecessor ? 753 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id(); 754 entry_->PutPredecessor(predecessor_id); 755 // Mark this entry as unsynced, to wake up the syncer. 756 MarkForSyncing(); 757} 758 759void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) { 760 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); 761 new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size()); 762 SetBookmarkSpecifics(new_value); 763} 764 765void WriteNode::MarkForSyncing() { 766 syncable::MarkForSyncing(entry_); 767} 768 769////////////////////////////////////////////////////////////////////////// 770// ReadNode member definitions 771ReadNode::ReadNode(const BaseTransaction* transaction) 772 : entry_(NULL), transaction_(transaction) { 773 DCHECK(transaction); 774} 775 776ReadNode::ReadNode() { 777 entry_ = NULL; 778 transaction_ = NULL; 779} 780 781ReadNode::~ReadNode() { 782 delete entry_; 783} 784 785void ReadNode::InitByRootLookup() { 786 DCHECK(!entry_) << "Init called twice"; 787 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); 788 entry_ = new syncable::Entry(trans, syncable::GET_BY_ID, trans->root_id()); 789 if (!entry_->good()) 790 DCHECK(false) << "Could not lookup root node for reading."; 791} 792 793bool ReadNode::InitByIdLookup(int64 id) { 794 DCHECK(!entry_) << "Init called twice"; 795 DCHECK_NE(id, kInvalidId); 796 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); 797 entry_ = new syncable::Entry(trans, syncable::GET_BY_HANDLE, id); 798 if (!entry_->good()) 799 return false; 800 if (entry_->Get(syncable::IS_DEL)) 801 return false; 802 syncable::ModelType model_type = GetModelType(); 803 LOG_IF(WARNING, model_type == syncable::UNSPECIFIED || 804 model_type == syncable::TOP_LEVEL_FOLDER) 805 << "SyncAPI InitByIdLookup referencing unusual object."; 806 return DecryptIfNecessary(entry_); 807} 808 809bool ReadNode::InitByClientTagLookup(syncable::ModelType model_type, 810 const std::string& tag) { 811 DCHECK(!entry_) << "Init called twice"; 812 if (tag.empty()) 813 return false; 814 815 const std::string hash = GenerateSyncableHash(model_type, tag); 816 817 entry_ = new syncable::Entry(transaction_->GetWrappedTrans(), 818 syncable::GET_BY_CLIENT_TAG, hash); 819 return (entry_->good() && !entry_->Get(syncable::IS_DEL) && 820 DecryptIfNecessary(entry_)); 821} 822 823const syncable::Entry* ReadNode::GetEntry() const { 824 return entry_; 825} 826 827const BaseTransaction* ReadNode::GetTransaction() const { 828 return transaction_; 829} 830 831bool ReadNode::InitByTagLookup(const std::string& tag) { 832 DCHECK(!entry_) << "Init called twice"; 833 if (tag.empty()) 834 return false; 835 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); 836 entry_ = new syncable::Entry(trans, syncable::GET_BY_SERVER_TAG, tag); 837 if (!entry_->good()) 838 return false; 839 if (entry_->Get(syncable::IS_DEL)) 840 return false; 841 syncable::ModelType model_type = GetModelType(); 842 LOG_IF(WARNING, model_type == syncable::UNSPECIFIED || 843 model_type == syncable::TOP_LEVEL_FOLDER) 844 << "SyncAPI InitByTagLookup referencing unusually typed object."; 845 return DecryptIfNecessary(entry_); 846} 847 848////////////////////////////////////////////////////////////////////////// 849// ReadTransaction member definitions 850ReadTransaction::ReadTransaction(UserShare* share) 851 : BaseTransaction(share), 852 transaction_(NULL), 853 close_transaction_(true) { 854 transaction_ = new syncable::ReadTransaction(GetLookup(), __FILE__, __LINE__); 855} 856 857ReadTransaction::ReadTransaction(UserShare* share, 858 syncable::BaseTransaction* trans) 859 : BaseTransaction(share), 860 transaction_(trans), 861 close_transaction_(false) {} 862 863ReadTransaction::~ReadTransaction() { 864 if (close_transaction_) { 865 delete transaction_; 866 } 867} 868 869syncable::BaseTransaction* ReadTransaction::GetWrappedTrans() const { 870 return transaction_; 871} 872 873////////////////////////////////////////////////////////////////////////// 874// WriteTransaction member definitions 875WriteTransaction::WriteTransaction(UserShare* share) 876 : BaseTransaction(share), 877 transaction_(NULL) { 878 transaction_ = new syncable::WriteTransaction(GetLookup(), syncable::SYNCAPI, 879 __FILE__, __LINE__); 880} 881 882WriteTransaction::~WriteTransaction() { 883 delete transaction_; 884} 885 886syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const { 887 return transaction_; 888} 889 890// A GaiaAuthenticator that uses HttpPostProviders instead of CURL. 891class BridgedGaiaAuthenticator : public gaia::GaiaAuthenticator { 892 public: 893 BridgedGaiaAuthenticator(const string& user_agent, const string& service_id, 894 const string& gaia_url, 895 HttpPostProviderFactory* factory) 896 : GaiaAuthenticator(user_agent, service_id, gaia_url), 897 gaia_source_(user_agent), post_factory_(factory) { 898 } 899 900 virtual ~BridgedGaiaAuthenticator() { 901 } 902 903 virtual bool Post(const GURL& url, const string& post_body, 904 unsigned long* response_code, string* response_body) { 905 string connection_url = "https://"; 906 connection_url += url.host(); 907 connection_url += url.path(); 908 HttpPostProviderInterface* http = post_factory_->Create(); 909 http->SetUserAgent(gaia_source_.c_str()); 910 // SSL is on 443 for Gaia Posts always. 911 http->SetURL(connection_url.c_str(), kSSLPort); 912 http->SetPostPayload("application/x-www-form-urlencoded", 913 post_body.length(), post_body.c_str()); 914 915 int os_error_code = 0; 916 int int_response_code = 0; 917 if (!http->MakeSynchronousPost(&os_error_code, &int_response_code)) { 918 VLOG(1) << "Http POST failed, error returns: " << os_error_code; 919 return false; 920 } 921 *response_code = static_cast<int>(int_response_code); 922 response_body->assign(http->GetResponseContent(), 923 http->GetResponseContentLength()); 924 post_factory_->Destroy(http); 925 return true; 926 } 927 928 virtual int GetBackoffDelaySeconds(int current_backoff_delay) { 929 return SyncerThread::GetRecommendedDelaySeconds(current_backoff_delay); 930 } 931 private: 932 const std::string gaia_source_; 933 scoped_ptr<HttpPostProviderFactory> post_factory_; 934 DISALLOW_COPY_AND_ASSIGN(BridgedGaiaAuthenticator); 935}; 936 937SyncManager::ChangeRecord::ChangeRecord() 938 : id(kInvalidId), action(ACTION_ADD) {} 939 940SyncManager::ChangeRecord::~ChangeRecord() {} 941 942SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData( 943 const sync_pb::PasswordSpecificsData& data) 944 : unencrypted_(data) { 945} 946 947SyncManager::ExtraPasswordChangeRecordData::~ExtraPasswordChangeRecordData() {} 948 949////////////////////////////////////////////////////////////////////////// 950// SyncManager's implementation: SyncManager::SyncInternal 951class SyncManager::SyncInternal 952 : public net::NetworkChangeNotifier::Observer, 953 public TalkMediator::Delegate, 954 public sync_notifier::StateWriter, 955 public browser_sync::ChannelEventHandler<syncable::DirectoryChangeEvent>, 956 public SyncEngineEventListener { 957 static const int kDefaultNudgeDelayMilliseconds; 958 static const int kPreferencesNudgeDelayMilliseconds; 959 public: 960 explicit SyncInternal(SyncManager* sync_manager) 961 : core_message_loop_(NULL), 962 observer_(NULL), 963 sync_manager_(sync_manager), 964 registrar_(NULL), 965 notification_pending_(false), 966 initialized_(false), 967 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { 968 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 969 } 970 971 virtual ~SyncInternal() { 972 DCHECK(!core_message_loop_); 973 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 974 } 975 976 bool Init(const FilePath& database_location, 977 const std::string& sync_server_and_path, 978 int port, 979 bool use_ssl, 980 HttpPostProviderFactory* post_factory, 981 ModelSafeWorkerRegistrar* model_safe_worker_registrar, 982 const char* user_agent, 983 const SyncCredentials& credentials, 984 const notifier::NotifierOptions& notifier_options, 985 const std::string& restored_key_for_bootstrapping, 986 bool setup_for_test_mode); 987 988 // Sign into sync with given credentials. 989 // We do not verify the tokens given. After this call, the tokens are set 990 // and the sync DB is open. True if successful, false if something 991 // went wrong. 992 bool SignIn(const SyncCredentials& credentials); 993 994 // Update tokens that we're using in Sync. Email must stay the same. 995 void UpdateCredentials(const SyncCredentials& credentials); 996 997 // Tell the sync engine to start the syncing process. 998 void StartSyncing(); 999 1000 // Whether or not the Nigori node is encrypted using an explicit passphrase. 1001 bool IsUsingExplicitPassphrase(); 1002 1003 // Try to set the current passphrase to |passphrase|, and record whether 1004 // it is an explicit passphrase or implicitly using gaia in the Nigori 1005 // node. 1006 void SetPassphrase(const std::string& passphrase, bool is_explicit); 1007 1008 // Call periodically from a database-safe thread to persist recent changes 1009 // to the syncapi model. 1010 void SaveChanges(); 1011 1012 // This listener is called upon completion of a syncable transaction, and 1013 // builds the list of sync-engine initiated changes that will be forwarded to 1014 // the SyncManager's Observers. 1015 virtual void HandleChannelEvent(const syncable::DirectoryChangeEvent& event); 1016 void HandleTransactionCompleteChangeEvent( 1017 const syncable::DirectoryChangeEvent& event); 1018 void HandleTransactionEndingChangeEvent( 1019 const syncable::DirectoryChangeEvent& event); 1020 void HandleCalculateChangesChangeEventFromSyncApi( 1021 const syncable::DirectoryChangeEvent& event); 1022 void HandleCalculateChangesChangeEventFromSyncer( 1023 const syncable::DirectoryChangeEvent& event); 1024 1025 // Listens for notifications from the ServerConnectionManager 1026 void HandleServerConnectionEvent(const ServerConnectionEvent& event); 1027 1028 // Open the directory named with username_for_share 1029 bool OpenDirectory(); 1030 1031 // Login to the talk mediator with the given credentials. 1032 void TalkMediatorLogin( 1033 const std::string& email, const std::string& token); 1034 1035 // TalkMediator::Delegate implementation. 1036 virtual void OnNotificationStateChange( 1037 bool notifications_enabled); 1038 1039 virtual void OnIncomingNotification( 1040 const IncomingNotificationData& notification_data); 1041 1042 virtual void OnOutgoingNotification(); 1043 1044 // sync_notifier::StateWriter implementation. 1045 virtual void WriteState(const std::string& state); 1046 1047 // Accessors for the private members. 1048 DirectoryManager* dir_manager() { return share_.dir_manager.get(); } 1049 SyncAPIServerConnectionManager* connection_manager() { 1050 return connection_manager_.get(); 1051 } 1052 SyncerThread* syncer_thread() { return syncer_thread_.get(); } 1053 TalkMediator* talk_mediator() { return talk_mediator_.get(); } 1054 void set_observer(SyncManager::Observer* observer) { observer_ = observer; } 1055 UserShare* GetUserShare() { return &share_; } 1056 1057 // Return the currently active (validated) username for use with syncable 1058 // types. 1059 const std::string& username_for_share() const { 1060 return share_.name; 1061 } 1062 1063 // Note about SyncManager::Status implementation: Status is a trimmed 1064 // down AllStatus::Status, augmented with authentication failure information 1065 // gathered from the internal AuthWatcher. The sync UI itself hooks up to 1066 // various sources like the AuthWatcher individually, but with syncapi we try 1067 // to keep everything status-related in one place. This means we have to 1068 // privately manage state about authentication failures, and whenever the 1069 // status or status summary is requested we aggregate this state with 1070 // AllStatus::Status information. 1071 Status ComputeAggregatedStatus(); 1072 Status::Summary ComputeAggregatedStatusSummary(); 1073 1074 // See SyncManager::Shutdown for information. 1075 void Shutdown(); 1076 1077 // Whether we're initialized to the point of being able to accept changes 1078 // (and hence allow transaction creation). See initialized_ for details. 1079 bool initialized() const { 1080 AutoLock lock(initialized_mutex_); 1081 return initialized_; 1082 } 1083 1084 void SetExtraChangeRecordData(int64 id, 1085 syncable::ModelType type, 1086 ChangeReorderBuffer* buffer, 1087 Cryptographer* cryptographer, 1088 const syncable::EntryKernel& original, 1089 bool existed_before, 1090 bool exists_now); 1091 1092 // Called only by our NetworkChangeNotifier. 1093 virtual void OnIPAddressChanged(); 1094 1095 bool InitialSyncEndedForAllEnabledTypes() { 1096 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1097 if (!lookup.good()) { 1098 DCHECK(false) << "ScopedDirLookup failed when checking initial sync"; 1099 return false; 1100 } 1101 1102 ModelSafeRoutingInfo enabled_types; 1103 registrar_->GetModelSafeRoutingInfo(&enabled_types); 1104 for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin(); 1105 i != enabled_types.end(); ++i) { 1106 if (!lookup->initial_sync_ended_for_type(i->first)) 1107 return false; 1108 } 1109 return true; 1110 } 1111 1112 syncable::AutofillMigrationState GetAutofillMigrationState() { 1113 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1114 if (!lookup.good()) { 1115 DCHECK(false) << "ScopedDirLookup failed when checking initial sync"; 1116 return syncable::NOT_MIGRATED; 1117 } 1118 1119 return lookup->get_autofill_migration_state(); 1120 } 1121 1122 void SetAutofillMigrationState(syncable::AutofillMigrationState state) { 1123 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1124 if (!lookup.good()) { 1125 DCHECK(false) << "ScopedDirLookup failed when checking initial sync"; 1126 return; 1127 } 1128 1129 return lookup->set_autofill_migration_state(state); 1130 } 1131 1132 void SetAutofillMigrationDebugInfo( 1133 syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set, 1134 const syncable::AutofillMigrationDebugInfo& info) { 1135 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1136 if (!lookup.good()) { 1137 DCHECK(false) << "ScopedDirLookup failed when checking initial sync"; 1138 return; 1139 } 1140 1141 return lookup->set_autofill_migration_state_debug_info( 1142 property_to_set, info); 1143 } 1144 1145 syncable::AutofillMigrationDebugInfo 1146 GetAutofillMigrationDebugInfo() { 1147 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1148 if (!lookup.good()) { 1149 DCHECK(false) << "ScopedDirLookup failed when checking initial sync"; 1150 syncable::AutofillMigrationDebugInfo null_value = {0}; 1151 return null_value; 1152 } 1153 return lookup->get_autofill_migration_debug_info(); 1154 } 1155 1156 // SyncEngineEventListener implementation. 1157 virtual void OnSyncEngineEvent(const SyncEngineEvent& event); 1158 private: 1159 // Helper to handle the details of initializing the TalkMediator. 1160 // Must be called only after OpenDirectory() is called. 1161 void InitializeTalkMediator(); 1162 1163 // Helper to call OnAuthError when no authentication credentials are 1164 // available. 1165 void RaiseAuthNeededEvent(); 1166 1167 // Helper to set initialized_ to true and raise an event to clients to notify 1168 // that initialization is complete and it is safe to send us changes. If 1169 // already initialized, this is a no-op. 1170 void MarkAndNotifyInitializationComplete(); 1171 1172 // If there's a pending notification to be sent, either from the 1173 // new_pending_notification flag or a previous unsuccessfully sent 1174 // notification, tries to send a notification. 1175 void SendPendingXMPPNotification(bool new_pending_notification); 1176 1177 // Determine if the parents or predecessors differ between the old and new 1178 // versions of an entry stored in |a| and |b|. Note that a node's index may 1179 // change without its NEXT_ID changing if the node at NEXT_ID also moved (but 1180 // the relative order is unchanged). To handle such cases, we rely on the 1181 // caller to treat a position update on any sibling as updating the positions 1182 // of all siblings. 1183 static bool VisiblePositionsDiffer(const syncable::EntryKernel& a, 1184 const syncable::Entry& b) { 1185 // If the datatype isn't one where the browser model cares about position, 1186 // don't bother notifying that data model of position-only changes. 1187 if (!b.ShouldMaintainPosition()) 1188 return false; 1189 if (a.ref(syncable::NEXT_ID) != b.Get(syncable::NEXT_ID)) 1190 return true; 1191 if (a.ref(syncable::PARENT_ID) != b.Get(syncable::PARENT_ID)) 1192 return true; 1193 return false; 1194 } 1195 1196 // Determine if any of the fields made visible to clients of the Sync API 1197 // differ between the versions of an entry stored in |a| and |b|. A return 1198 // value of false means that it should be OK to ignore this change. 1199 static bool VisiblePropertiesDiffer(const syncable::EntryKernel& a, 1200 const syncable::Entry& b) { 1201 syncable::ModelType model_type = b.GetModelType(); 1202 // Suppress updates to items that aren't tracked by any browser model. 1203 if (model_type == syncable::UNSPECIFIED || 1204 model_type == syncable::TOP_LEVEL_FOLDER) { 1205 return false; 1206 } 1207 if (a.ref(syncable::NON_UNIQUE_NAME) != b.Get(syncable::NON_UNIQUE_NAME)) 1208 return true; 1209 if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR)) 1210 return true; 1211 if (a.ref(SPECIFICS).SerializeAsString() != 1212 b.Get(SPECIFICS).SerializeAsString()) { 1213 return true; 1214 } 1215 if (VisiblePositionsDiffer(a, b)) 1216 return true; 1217 return false; 1218 } 1219 1220 bool ChangeBuffersAreEmpty() { 1221 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { 1222 if (!change_buffers_[i].IsEmpty()) 1223 return false; 1224 } 1225 return true; 1226 } 1227 1228 void CheckServerReachable() { 1229 if (connection_manager()) { 1230 connection_manager()->CheckServerReachable(); 1231 } else { 1232 NOTREACHED() << "Should be valid connection manager!"; 1233 } 1234 } 1235 1236 void ReEncryptEverything(WriteTransaction* trans); 1237 1238 // Initializes (bootstraps) the Cryptographer if NIGORI has finished 1239 // initial sync so that it can immediately start encrypting / decrypting. 1240 // If the restored key is incompatible with the current version of the NIGORI 1241 // node (which could happen if a restart occurred just after an update to 1242 // NIGORI was downloaded and the user must enter a new passphrase to decrypt) 1243 // then we will raise OnPassphraseRequired and set pending keys for 1244 // decryption. Otherwise, the cryptographer is made ready (is_ready()). 1245 void BootstrapEncryption(const std::string& restored_key_for_bootstrapping); 1246 1247 // Helper for migration to new nigori proto to set 1248 // 'using_explicit_passphrase' in the NigoriSpecifics. 1249 // TODO(tim): Bug 62103. Remove this after it has been pushed out to dev 1250 // channel users. 1251 void SetUsingExplicitPassphrasePrefForMigration(); 1252 1253 // Checks for server reachabilty and requests a nudge. 1254 void OnIPAddressChangedImpl(); 1255 1256 // We couple the DirectoryManager and username together in a UserShare member 1257 // so we can return a handle to share_ to clients of the API for use when 1258 // constructing any transaction type. 1259 UserShare share_; 1260 1261 MessageLoop* core_message_loop_; 1262 1263 // Observer registered via SetObserver/RemoveObserver. 1264 // WARNING: This can be NULL! 1265 SyncManager::Observer* observer_; 1266 1267 // The ServerConnectionManager used to abstract communication between the 1268 // client (the Syncer) and the sync server. 1269 scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; 1270 1271 // The thread that runs the Syncer. Needs to be explicitly Start()ed. 1272 scoped_refptr<SyncerThread> syncer_thread_; 1273 1274 // Notification (xmpp) handler. 1275 scoped_ptr<TalkMediator> talk_mediator_; 1276 1277 // A multi-purpose status watch object that aggregates stats from various 1278 // sync components. 1279 AllStatus allstatus_; 1280 1281 // Each element of this array is a store of change records produced by 1282 // HandleChangeEvent during the CALCULATE_CHANGES step. The changes are 1283 // segregated by model type, and are stored here to be processed and 1284 // forwarded to the observer slightly later, at the TRANSACTION_ENDING 1285 // step by HandleTransactionEndingChangeEvent. The list is cleared in the 1286 // TRANSACTION_COMPLETE step by HandleTransactionCompleteChangeEvent. 1287 ChangeReorderBuffer change_buffers_[syncable::MODEL_TYPE_COUNT]; 1288 1289 // Bit vector keeping track of which models need to have their 1290 // OnChangesComplete observer set. 1291 // 1292 // Set by HandleTransactionEndingChangeEvent, cleared in 1293 // HandleTransactionCompleteChangeEvent. 1294 std::bitset<syncable::MODEL_TYPE_COUNT> model_has_change_; 1295 1296 // The event listener hookup that is registered for HandleChangeEvent. 1297 scoped_ptr<browser_sync::ChannelHookup<syncable::DirectoryChangeEvent> > 1298 dir_change_hookup_; 1299 1300 // Event listener hookup for the ServerConnectionManager. 1301 scoped_ptr<EventListenerHookup> connection_manager_hookup_; 1302 1303 // The sync dir_manager to which we belong. 1304 SyncManager* const sync_manager_; 1305 1306 // The entity that provides us with information about which types to sync. 1307 // The instance is shared between the SyncManager and the Syncer. 1308 ModelSafeWorkerRegistrar* registrar_; 1309 1310 // True if the next SyncCycle should notify peers of an update. 1311 bool notification_pending_; 1312 1313 // Set to true once Init has been called, and we know of an authenticated 1314 // valid) username either from a fresh authentication attempt (as in 1315 // first-use case) or from a previous attempt stored in our UserSettings 1316 // (as in the steady-state), and the syncable::Directory has been opened, 1317 // meaning we are ready to accept changes. Protected by initialized_mutex_ 1318 // as it can get read/set by both the SyncerThread and the AuthWatcherThread. 1319 bool initialized_; 1320 mutable Lock initialized_mutex_; 1321 1322 notifier::NotifierOptions notifier_options_; 1323 1324 // True if the SyncManager should be running in test mode (no syncer thread 1325 // actually communicating with the server). 1326 bool setup_for_test_mode_; 1327 1328 ScopedRunnableMethodFactory<SyncManager::SyncInternal> method_factory_; 1329}; 1330const int SyncManager::SyncInternal::kDefaultNudgeDelayMilliseconds = 200; 1331const int SyncManager::SyncInternal::kPreferencesNudgeDelayMilliseconds = 2000; 1332 1333SyncManager::SyncManager() { 1334 data_ = new SyncInternal(this); 1335} 1336 1337bool SyncManager::Init(const FilePath& database_location, 1338 const char* sync_server_and_path, 1339 int sync_server_port, 1340 bool use_ssl, 1341 HttpPostProviderFactory* post_factory, 1342 ModelSafeWorkerRegistrar* registrar, 1343 const char* user_agent, 1344 const SyncCredentials& credentials, 1345 const notifier::NotifierOptions& notifier_options, 1346 const std::string& restored_key_for_bootstrapping, 1347 bool setup_for_test_mode) { 1348 DCHECK(post_factory); 1349 VLOG(1) << "SyncManager starting Init..."; 1350 string server_string(sync_server_and_path); 1351 return data_->Init(database_location, 1352 server_string, 1353 sync_server_port, 1354 use_ssl, 1355 post_factory, 1356 registrar, 1357 user_agent, 1358 credentials, 1359 notifier_options, 1360 restored_key_for_bootstrapping, 1361 setup_for_test_mode); 1362} 1363 1364void SyncManager::UpdateCredentials(const SyncCredentials& credentials) { 1365 data_->UpdateCredentials(credentials); 1366} 1367 1368 1369bool SyncManager::InitialSyncEndedForAllEnabledTypes() { 1370 return data_->InitialSyncEndedForAllEnabledTypes(); 1371} 1372 1373void SyncManager::StartSyncing() { 1374 data_->StartSyncing(); 1375} 1376 1377syncable::AutofillMigrationState 1378 SyncManager::GetAutofillMigrationState() { 1379 return data_->GetAutofillMigrationState(); 1380} 1381 1382void SyncManager::SetAutofillMigrationState( 1383 syncable::AutofillMigrationState state) { 1384 return data_->SetAutofillMigrationState(state); 1385} 1386 1387syncable::AutofillMigrationDebugInfo 1388 SyncManager::GetAutofillMigrationDebugInfo() { 1389 return data_->GetAutofillMigrationDebugInfo(); 1390} 1391 1392void SyncManager::SetAutofillMigrationDebugInfo( 1393 syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set, 1394 const syncable::AutofillMigrationDebugInfo& info) { 1395 return data_->SetAutofillMigrationDebugInfo(property_to_set, info); 1396} 1397 1398void SyncManager::SetPassphrase(const std::string& passphrase, 1399 bool is_explicit) { 1400 data_->SetPassphrase(passphrase, is_explicit); 1401} 1402 1403bool SyncManager::IsUsingExplicitPassphrase() { 1404 return data_ && data_->IsUsingExplicitPassphrase(); 1405} 1406 1407bool SyncManager::RequestPause() { 1408 if (data_->syncer_thread()) 1409 return data_->syncer_thread()->RequestPause(); 1410 return false; 1411} 1412 1413bool SyncManager::RequestResume() { 1414 if (data_->syncer_thread()) 1415 return data_->syncer_thread()->RequestResume(); 1416 return false; 1417} 1418 1419void SyncManager::RequestNudge() { 1420 if (data_->syncer_thread()) 1421 data_->syncer_thread()->NudgeSyncer(0, SyncerThread::kLocal); 1422} 1423 1424void SyncManager::RequestClearServerData() { 1425 if (data_->syncer_thread()) 1426 data_->syncer_thread()->NudgeSyncer(0, SyncerThread::kClearPrivateData); 1427} 1428 1429const std::string& SyncManager::GetAuthenticatedUsername() { 1430 DCHECK(data_); 1431 return data_->username_for_share(); 1432} 1433 1434bool SyncManager::SyncInternal::Init( 1435 const FilePath& database_location, 1436 const std::string& sync_server_and_path, 1437 int port, 1438 bool use_ssl, 1439 HttpPostProviderFactory* post_factory, 1440 ModelSafeWorkerRegistrar* model_safe_worker_registrar, 1441 const char* user_agent, 1442 const SyncCredentials& credentials, 1443 const notifier::NotifierOptions& notifier_options, 1444 const std::string& restored_key_for_bootstrapping, 1445 bool setup_for_test_mode) { 1446 1447 VLOG(1) << "Starting SyncInternal initialization."; 1448 1449 core_message_loop_ = MessageLoop::current(); 1450 DCHECK(core_message_loop_); 1451 notifier_options_ = notifier_options; 1452 registrar_ = model_safe_worker_registrar; 1453 setup_for_test_mode_ = setup_for_test_mode; 1454 1455 share_.dir_manager.reset(new DirectoryManager(database_location)); 1456 1457 connection_manager_.reset(new SyncAPIServerConnectionManager( 1458 sync_server_and_path, port, use_ssl, user_agent, post_factory)); 1459 1460 connection_manager_hookup_.reset( 1461 NewEventListenerHookup(connection_manager()->channel(), this, 1462 &SyncManager::SyncInternal::HandleServerConnectionEvent)); 1463 1464 net::NetworkChangeNotifier::AddObserver(this); 1465 // TODO(akalin): CheckServerReachable() can block, which may cause jank if we 1466 // try to shut down sync. Fix this. 1467 core_message_loop_->PostTask(FROM_HERE, 1468 method_factory_.NewRunnableMethod(&SyncInternal::CheckServerReachable)); 1469 1470 // Test mode does not use a syncer context or syncer thread. 1471 if (!setup_for_test_mode) { 1472 // Build a SyncSessionContext and store the worker in it. 1473 VLOG(1) << "Sync is bringing up SyncSessionContext."; 1474 std::vector<SyncEngineEventListener*> listeners; 1475 listeners.push_back(&allstatus_); 1476 listeners.push_back(this); 1477 SyncSessionContext* context = new SyncSessionContext( 1478 connection_manager_.get(), 1479 dir_manager(), 1480 model_safe_worker_registrar, 1481 listeners); 1482 1483 // The SyncerThread takes ownership of |context|. 1484 syncer_thread_ = new SyncerThread(context); 1485 } 1486 1487 bool signed_in = SignIn(credentials); 1488 1489 // Do this once the directory is opened. 1490 BootstrapEncryption(restored_key_for_bootstrapping); 1491 return signed_in; 1492} 1493 1494void SyncManager::SyncInternal::BootstrapEncryption( 1495 const std::string& restored_key_for_bootstrapping) { 1496 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1497 if (!lookup.good()) { 1498 NOTREACHED(); 1499 return; 1500 } 1501 1502 if (!lookup->initial_sync_ended_for_type(syncable::NIGORI)) 1503 return; 1504 1505 Cryptographer* cryptographer = share_.dir_manager->cryptographer(); 1506 cryptographer->Bootstrap(restored_key_for_bootstrapping); 1507 1508 ReadTransaction trans(GetUserShare()); 1509 ReadNode node(&trans); 1510 if (!node.InitByTagLookup(kNigoriTag)) { 1511 NOTREACHED(); 1512 return; 1513 } 1514 1515 const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); 1516 if (!nigori.encrypted().blob().empty()) { 1517 if (cryptographer->CanDecrypt(nigori.encrypted())) { 1518 cryptographer->SetKeys(nigori.encrypted()); 1519 } else { 1520 cryptographer->SetPendingKeys(nigori.encrypted()); 1521 observer_->OnPassphraseRequired(true); 1522 } 1523 } 1524} 1525 1526void SyncManager::SyncInternal::StartSyncing() { 1527 if (syncer_thread()) // NULL during certain unittests. 1528 syncer_thread()->Start(); // Start the syncer thread. This won't actually 1529 // result in any syncing until at least the 1530 // DirectoryManager broadcasts the OPENED event, 1531 // and a valid server connection is detected. 1532} 1533 1534void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() { 1535 // There is only one real time we need this mutex. If we get an auth 1536 // success, and before the initial sync ends we get an auth failure. In this 1537 // case we'll be listening to both the AuthWatcher and Syncer, and it's a race 1538 // between their respective threads to call MarkAndNotify. We need to make 1539 // sure the observer is notified once and only once. 1540 { 1541 AutoLock lock(initialized_mutex_); 1542 if (initialized_) 1543 return; 1544 initialized_ = true; 1545 } 1546 1547 // Notify that initialization is complete. 1548 if (observer_) 1549 observer_->OnInitializationComplete(); 1550} 1551 1552void SyncManager::SyncInternal::SendPendingXMPPNotification( 1553 bool new_pending_notification) { 1554 DCHECK_EQ(MessageLoop::current(), core_message_loop_); 1555 DCHECK_NE(notifier_options_.notification_method, 1556 notifier::NOTIFICATION_SERVER); 1557 notification_pending_ = notification_pending_ || new_pending_notification; 1558 if (!notification_pending_) { 1559 VLOG(1) << "Not sending notification: no pending notification"; 1560 return; 1561 } 1562 if (!talk_mediator_.get()) { 1563 VLOG(1) << "Not sending notification: shutting down (talk_mediator_ is " 1564 "NULL)"; 1565 return; 1566 } 1567 VLOG(1) << "Sending XMPP notification..."; 1568 OutgoingNotificationData notification_data; 1569 notification_data.service_id = browser_sync::kSyncServiceId; 1570 notification_data.service_url = browser_sync::kSyncServiceUrl; 1571 notification_data.send_content = true; 1572 notification_data.priority = browser_sync::kSyncPriority; 1573 notification_data.write_to_cache_only = true; 1574 notification_data.service_specific_data = 1575 browser_sync::kSyncServiceSpecificData; 1576 notification_data.require_subscription = true; 1577 bool success = talk_mediator_->SendNotification(notification_data); 1578 if (success) { 1579 notification_pending_ = false; 1580 VLOG(1) << "Sent XMPP notification"; 1581 } else { 1582 VLOG(1) << "Could not send XMPP notification"; 1583 } 1584} 1585 1586bool SyncManager::SyncInternal::OpenDirectory() { 1587 DCHECK(!initialized()) << "Should only happen once"; 1588 1589 bool share_opened = dir_manager()->Open(username_for_share()); 1590 DCHECK(share_opened); 1591 if (!share_opened) { 1592 if (observer_) 1593 observer_->OnStopSyncingPermanently(); 1594 1595 LOG(ERROR) << "Could not open share for:" << username_for_share(); 1596 return false; 1597 } 1598 1599 // Database has to be initialized for the guid to be available. 1600 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1601 if (!lookup.good()) { 1602 NOTREACHED(); 1603 return false; 1604 } 1605 1606 connection_manager()->set_client_id(lookup->cache_guid()); 1607 1608 if (syncer_thread()) 1609 syncer_thread()->CreateSyncer(username_for_share()); 1610 1611 MarkAndNotifyInitializationComplete(); 1612 dir_change_hookup_.reset(lookup->AddChangeObserver(this)); 1613 return true; 1614} 1615 1616bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) { 1617 DCHECK_EQ(MessageLoop::current(), core_message_loop_); 1618 DCHECK(share_.name.empty()); 1619 share_.name = credentials.email; 1620 1621 VLOG(1) << "Signing in user: " << username_for_share(); 1622 if (!OpenDirectory()) 1623 return false; 1624 1625 UpdateCredentials(credentials); 1626 return true; 1627} 1628 1629void SyncManager::SyncInternal::UpdateCredentials( 1630 const SyncCredentials& credentials) { 1631 DCHECK_EQ(MessageLoop::current(), core_message_loop_); 1632 DCHECK(share_.name == credentials.email); 1633 connection_manager()->set_auth_token(credentials.sync_token); 1634 TalkMediatorLogin(credentials.email, credentials.sync_token); 1635 CheckServerReachable(); 1636 sync_manager_->RequestNudge(); 1637} 1638 1639void SyncManager::SyncInternal::InitializeTalkMediator() { 1640 if (notifier_options_.notification_method == 1641 notifier::NOTIFICATION_SERVER) { 1642 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 1643 std::string state; 1644 if (lookup.good()) 1645 state = lookup->GetAndClearNotificationState(); 1646 else 1647 LOG(ERROR) << "Could not read notification state"; 1648 if (VLOG_IS_ON(1)) { 1649 std::string encoded_state; 1650 base::Base64Encode(state, &encoded_state); 1651 VLOG(1) << "Read notification state: " << encoded_state; 1652 } 1653 sync_notifier::ServerNotifierThread* server_notifier_thread = 1654 new sync_notifier::ServerNotifierThread( 1655 notifier_options_, state, this); 1656 talk_mediator_.reset( 1657 new TalkMediatorImpl(server_notifier_thread, 1658 notifier_options_.invalidate_xmpp_login, 1659 notifier_options_.allow_insecure_connection)); 1660 } else { 1661 notifier::MediatorThread* mediator_thread = 1662 new notifier::MediatorThreadImpl(notifier_options_); 1663 talk_mediator_.reset( 1664 new TalkMediatorImpl(mediator_thread, 1665 notifier_options_.invalidate_xmpp_login, 1666 notifier_options_.allow_insecure_connection)); 1667 talk_mediator_->AddSubscribedServiceUrl(browser_sync::kSyncServiceUrl); 1668 } 1669 talk_mediator_->SetDelegate(this); 1670} 1671 1672void SyncManager::SyncInternal::RaiseAuthNeededEvent() { 1673 if (observer_) { 1674 observer_->OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS)); 1675 } 1676} 1677 1678void SyncManager::SyncInternal::SetUsingExplicitPassphrasePrefForMigration() { 1679 WriteTransaction trans(&share_); 1680 WriteNode node(&trans); 1681 if (!node.InitByTagLookup(kNigoriTag)) { 1682 // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. 1683 NOTREACHED(); 1684 return; 1685 } 1686 sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics()); 1687 specifics.set_using_explicit_passphrase(true); 1688 node.SetNigoriSpecifics(specifics); 1689} 1690 1691void SyncManager::SyncInternal::SetPassphrase( 1692 const std::string& passphrase, bool is_explicit) { 1693 Cryptographer* cryptographer = dir_manager()->cryptographer(); 1694 KeyParams params = {"localhost", "dummy", passphrase}; 1695 if (cryptographer->has_pending_keys()) { 1696 if (!cryptographer->DecryptPendingKeys(params)) { 1697 observer_->OnPassphraseRequired(true); 1698 return; 1699 } 1700 1701 // TODO(tim): If this is the first time the user has entered a passphrase 1702 // since the protocol changed to store passphrase preferences in the cloud, 1703 // make sure we update this preference. See bug 62103. 1704 if (is_explicit) 1705 SetUsingExplicitPassphrasePrefForMigration(); 1706 1707 // Nudge the syncer so that passwords updates that were waiting for this 1708 // passphrase get applied as soon as possible. 1709 sync_manager_->RequestNudge(); 1710 } else { 1711 WriteTransaction trans(GetUserShare()); 1712 WriteNode node(&trans); 1713 if (!node.InitByTagLookup(kNigoriTag)) { 1714 // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. 1715 NOTREACHED(); 1716 return; 1717 } 1718 1719 // Prevent an implicit SetPassphrase request from changing an explicitly 1720 // set passphrase. 1721 if (!is_explicit && node.GetNigoriSpecifics().using_explicit_passphrase()) 1722 return; 1723 1724 cryptographer->AddKey(params); 1725 1726 // TODO(tim): Bug 58231. It would be nice if SetPassphrase didn't require 1727 // messing with the Nigori node, because we can't call SetPassphrase until 1728 // download conditions are met vs Cryptographer init. It seems like it's 1729 // safe to defer this work. 1730 sync_pb::NigoriSpecifics specifics; 1731 cryptographer->GetKeys(specifics.mutable_encrypted()); 1732 specifics.set_using_explicit_passphrase(is_explicit); 1733 node.SetNigoriSpecifics(specifics); 1734 ReEncryptEverything(&trans); 1735 } 1736 1737 std::string bootstrap_token; 1738 cryptographer->GetBootstrapToken(&bootstrap_token); 1739 observer_->OnPassphraseAccepted(bootstrap_token); 1740} 1741 1742bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() { 1743 ReadTransaction trans(&share_); 1744 ReadNode node(&trans); 1745 if (!node.InitByTagLookup(kNigoriTag)) { 1746 // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. 1747 NOTREACHED(); 1748 return false; 1749 } 1750 1751 return node.GetNigoriSpecifics().using_explicit_passphrase(); 1752} 1753 1754void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { 1755 // TODO(tim): bug 59242. We shouldn't lookup by data type and instead use 1756 // a protocol flag or existence of an EncryptedData message, but for now, 1757 // encryption is on if-and-only-if the type is passwords, and we haven't 1758 // ironed out the protocol for generic encryption. 1759 static const char* passwords_tag = "google_chrome_passwords"; 1760 ReadNode passwords_root(trans); 1761 if (!passwords_root.InitByTagLookup(passwords_tag)) { 1762 LOG(WARNING) << "No passwords to reencrypt."; 1763 return; 1764 } 1765 1766 int64 child_id = passwords_root.GetFirstChildId(); 1767 while (child_id != kInvalidId) { 1768 WriteNode child(trans); 1769 if (!child.InitByIdLookup(child_id)) { 1770 NOTREACHED(); 1771 return; 1772 } 1773 child.SetPasswordSpecifics(child.GetPasswordSpecifics()); 1774 child_id = child.GetSuccessorId(); 1775 } 1776} 1777 1778SyncManager::~SyncManager() { 1779 delete data_; 1780} 1781 1782void SyncManager::SetObserver(Observer* observer) { 1783 data_->set_observer(observer); 1784} 1785 1786void SyncManager::RemoveObserver() { 1787 data_->set_observer(NULL); 1788} 1789 1790void SyncManager::Shutdown() { 1791 data_->Shutdown(); 1792} 1793 1794void SyncManager::SyncInternal::Shutdown() { 1795 method_factory_.RevokeAll(); 1796 1797 // We NULL out talk_mediator_ so that any tasks pumped below do not 1798 // trigger further XMPP actions. 1799 // 1800 // TODO(akalin): NULL the other member variables defensively, too. 1801 scoped_ptr<TalkMediator> talk_mediator(talk_mediator_.release()); 1802 1803 if (syncer_thread()) { 1804 if (!syncer_thread()->Stop(kThreadExitTimeoutMsec)) { 1805 LOG(FATAL) << "Unable to stop the syncer, it won't be happy..."; 1806 } 1807 syncer_thread_ = NULL; 1808 } 1809 1810 // Shutdown the xmpp buzz connection. 1811 if (talk_mediator.get()) { 1812 VLOG(1) << "P2P: Mediator logout started."; 1813 talk_mediator->Logout(); 1814 VLOG(1) << "P2P: Mediator logout completed."; 1815 talk_mediator.reset(); 1816 VLOG(1) << "P2P: Mediator destroyed."; 1817 } 1818 1819 // Pump any messages the auth watcher, syncer thread, or talk 1820 // mediator posted before they shut down. (See OnSyncEngineEvent(), 1821 // and HandleTalkMediatorEvent() for the 1822 // events that may be posted.) 1823 { 1824 CHECK(core_message_loop_); 1825 bool old_state = core_message_loop_->NestableTasksAllowed(); 1826 core_message_loop_->SetNestableTasksAllowed(true); 1827 core_message_loop_->RunAllPending(); 1828 core_message_loop_->SetNestableTasksAllowed(old_state); 1829 } 1830 1831 net::NetworkChangeNotifier::RemoveObserver(this); 1832 1833 connection_manager_hookup_.reset(); 1834 1835 if (dir_manager()) { 1836 dir_manager()->FinalSaveChangesForAll(); 1837 dir_manager()->Close(username_for_share()); 1838 } 1839 1840 // Reset the DirectoryManager and UserSettings so they relinquish sqlite 1841 // handles to backing files. 1842 share_.dir_manager.reset(); 1843 1844 // We don't want to process any more events. 1845 dir_change_hookup_.reset(); 1846 1847 core_message_loop_ = NULL; 1848} 1849 1850void SyncManager::SyncInternal::OnIPAddressChanged() { 1851 VLOG(1) << "IP address change detected"; 1852#if defined (OS_CHROMEOS) 1853 // TODO(tim): This is a hack to intentionally lose a race with flimflam at 1854 // shutdown, so we don't cause shutdown to wait for our http request. 1855 // http://crosbug.com/8429 1856 MessageLoop::current()->PostDelayedTask(FROM_HERE, 1857 method_factory_.NewRunnableMethod(&SyncInternal::OnIPAddressChangedImpl), 1858 kChromeOSNetworkChangeReactionDelayHackMsec); 1859#else 1860 OnIPAddressChangedImpl(); 1861#endif // defined(OS_CHROMEOS) 1862} 1863 1864void SyncManager::SyncInternal::OnIPAddressChangedImpl() { 1865 // TODO(akalin): CheckServerReachable() can block, which may cause 1866 // jank if we try to shut down sync. Fix this. 1867 connection_manager()->CheckServerReachable(); 1868 sync_manager_->RequestNudge(); 1869} 1870 1871// Listen to model changes, filter out ones initiated by the sync API, and 1872// saves the rest (hopefully just backend Syncer changes resulting from 1873// ApplyUpdates) to data_->changelist. 1874void SyncManager::SyncInternal::HandleChannelEvent( 1875 const syncable::DirectoryChangeEvent& event) { 1876 if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE) { 1877 // Safe to perform slow I/O operations now, go ahead and commit. 1878 HandleTransactionCompleteChangeEvent(event); 1879 return; 1880 } else if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_ENDING) { 1881 HandleTransactionEndingChangeEvent(event); 1882 return; 1883 } else if (event.todo == syncable::DirectoryChangeEvent::CALCULATE_CHANGES) { 1884 if (event.writer == syncable::SYNCAPI) { 1885 HandleCalculateChangesChangeEventFromSyncApi(event); 1886 return; 1887 } 1888 HandleCalculateChangesChangeEventFromSyncer(event); 1889 return; 1890 } else if (event.todo == syncable::DirectoryChangeEvent::SHUTDOWN) { 1891 dir_change_hookup_.reset(); 1892 } 1893} 1894 1895void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( 1896 const syncable::DirectoryChangeEvent& event) { 1897 // This notification happens immediately after the channel mutex is released 1898 // This allows work to be performed without holding the WriteTransaction lock 1899 // but before the transaction is finished. 1900 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE); 1901 if (!observer_) 1902 return; 1903 1904 // Call commit 1905 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { 1906 if (model_has_change_.test(i)) { 1907 observer_->OnChangesComplete(syncable::ModelTypeFromInt(i)); 1908 model_has_change_.reset(i); 1909 } 1910 } 1911} 1912 1913void SyncManager::SyncInternal::HandleServerConnectionEvent( 1914 const ServerConnectionEvent& event) { 1915 allstatus_.HandleServerConnectionEvent(event); 1916 if (event.what_happened == ServerConnectionEvent::STATUS_CHANGED) { 1917 if (event.connection_code == 1918 browser_sync::HttpResponse::SERVER_CONNECTION_OK) { 1919 if (observer_) { 1920 observer_->OnAuthError(AuthError::None()); 1921 } 1922 } 1923 1924 if (event.connection_code == browser_sync::HttpResponse::SYNC_AUTH_ERROR) { 1925 if (observer_) { 1926 observer_->OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS)); 1927 } 1928 } 1929 } 1930} 1931 1932void SyncManager::SyncInternal::HandleTransactionEndingChangeEvent( 1933 const syncable::DirectoryChangeEvent& event) { 1934 // This notification happens immediately before a syncable WriteTransaction 1935 // falls out of scope. It happens while the channel mutex is still held, 1936 // and while the transaction mutex is held, so it cannot be re-entrant. 1937 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_ENDING); 1938 if (!observer_ || ChangeBuffersAreEmpty()) 1939 return; 1940 1941 // This will continue the WriteTransaction using a read only wrapper. 1942 // This is the last chance for read to occur in the WriteTransaction 1943 // that's closing. This special ReadTransaction will not close the 1944 // underlying transaction. 1945 ReadTransaction trans(GetUserShare(), event.trans); 1946 1947 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { 1948 if (change_buffers_[i].IsEmpty()) 1949 continue; 1950 1951 vector<ChangeRecord> ordered_changes; 1952 change_buffers_[i].GetAllChangesInTreeOrder(&trans, &ordered_changes); 1953 if (!ordered_changes.empty()) { 1954 observer_->OnChangesApplied(syncable::ModelTypeFromInt(i), &trans, 1955 &ordered_changes[0], ordered_changes.size()); 1956 model_has_change_.set(i, true); 1957 } 1958 change_buffers_[i].Clear(); 1959 } 1960} 1961 1962void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( 1963 const syncable::DirectoryChangeEvent& event) { 1964 // We have been notified about a user action changing the bookmark model. 1965 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); 1966 DCHECK(event.writer == syncable::SYNCAPI || 1967 event.writer == syncable::UNITTEST); 1968 LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << 1969 "CALCULATE_CHANGES called with unapplied old changes."; 1970 1971 bool exists_unsynced_items = false; 1972 bool only_preference_changes = true; 1973 syncable::ModelTypeBitSet model_types; 1974 for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); 1975 i != event.originals->end() && !exists_unsynced_items; 1976 ++i) { 1977 int64 id = i->ref(syncable::META_HANDLE); 1978 syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); 1979 DCHECK(e.good()); 1980 1981 syncable::ModelType model_type = e.GetModelType(); 1982 1983 if (e.Get(syncable::IS_UNSYNCED)) { 1984 if (model_type == syncable::TOP_LEVEL_FOLDER || 1985 model_type == syncable::UNSPECIFIED) { 1986 NOTREACHED() << "Permanent or underspecified item changed via syncapi."; 1987 continue; 1988 } 1989 // Unsynced items will cause us to nudge the the syncer. 1990 exists_unsynced_items = true; 1991 1992 model_types[model_type] = true; 1993 if (model_type != syncable::PREFERENCES) 1994 only_preference_changes = false; 1995 } 1996 } 1997 if (exists_unsynced_items && syncer_thread()) { 1998 int nudge_delay = only_preference_changes ? 1999 kPreferencesNudgeDelayMilliseconds : kDefaultNudgeDelayMilliseconds; 2000 syncer_thread()->NudgeSyncerWithDataTypes( 2001 nudge_delay, 2002 SyncerThread::kLocal, 2003 model_types); 2004 } 2005} 2006 2007void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, 2008 syncable::ModelType type, ChangeReorderBuffer* buffer, 2009 Cryptographer* cryptographer, const syncable::EntryKernel& original, 2010 bool existed_before, bool exists_now) { 2011 // If this is a deletion, attach the entity specifics as extra data 2012 // so that the delete can be processed. 2013 if (!exists_now && existed_before) { 2014 buffer->SetSpecificsForId(id, original.ref(SPECIFICS)); 2015 if (type == syncable::PASSWORDS) { 2016 // Need to dig a bit deeper as passwords are encrypted. 2017 scoped_ptr<sync_pb::PasswordSpecificsData> data( 2018 DecryptPasswordSpecifics(original.ref(SPECIFICS), cryptographer)); 2019 if (!data.get()) { 2020 NOTREACHED(); 2021 return; 2022 } 2023 buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); 2024 } 2025 } 2026} 2027 2028void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( 2029 const syncable::DirectoryChangeEvent& event) { 2030 // We only expect one notification per sync step, so change_buffers_ should 2031 // contain no pending entries. 2032 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); 2033 DCHECK(event.writer == syncable::SYNCER || 2034 event.writer == syncable::UNITTEST); 2035 LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << 2036 "CALCULATE_CHANGES called with unapplied old changes."; 2037 2038 for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); 2039 i != event.originals->end(); ++i) { 2040 int64 id = i->ref(syncable::META_HANDLE); 2041 syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); 2042 bool existed_before = !i->ref(syncable::IS_DEL); 2043 bool exists_now = e.good() && !e.Get(syncable::IS_DEL); 2044 DCHECK(e.good()); 2045 2046 // Omit items that aren't associated with a model. 2047 syncable::ModelType type = e.GetModelType(); 2048 if (type == syncable::TOP_LEVEL_FOLDER || type == syncable::UNSPECIFIED) 2049 continue; 2050 2051 if (exists_now && !existed_before) 2052 change_buffers_[type].PushAddedItem(id); 2053 else if (!exists_now && existed_before) 2054 change_buffers_[type].PushDeletedItem(id); 2055 else if (exists_now && existed_before && VisiblePropertiesDiffer(*i, e)) 2056 change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e)); 2057 2058 SetExtraChangeRecordData(id, type, &change_buffers_[type], 2059 dir_manager()->cryptographer(), *i, 2060 existed_before, exists_now); 2061 } 2062} 2063 2064SyncManager::Status::Summary 2065SyncManager::SyncInternal::ComputeAggregatedStatusSummary() { 2066 switch (allstatus_.status().icon) { 2067 case AllStatus::OFFLINE: 2068 return Status::OFFLINE; 2069 case AllStatus::OFFLINE_UNSYNCED: 2070 return Status::OFFLINE_UNSYNCED; 2071 case AllStatus::SYNCING: 2072 return Status::SYNCING; 2073 case AllStatus::READY: 2074 return Status::READY; 2075 case AllStatus::CONFLICT: 2076 return Status::CONFLICT; 2077 case AllStatus::OFFLINE_UNUSABLE: 2078 return Status::OFFLINE_UNUSABLE; 2079 default: 2080 return Status::INVALID; 2081 } 2082} 2083 2084SyncManager::Status SyncManager::SyncInternal::ComputeAggregatedStatus() { 2085 Status return_status = 2086 { ComputeAggregatedStatusSummary(), 2087 allstatus_.status().authenticated, 2088 allstatus_.status().server_up, 2089 allstatus_.status().server_reachable, 2090 allstatus_.status().server_broken, 2091 allstatus_.status().notifications_enabled, 2092 allstatus_.status().notifications_received, 2093 allstatus_.status().notifications_sent, 2094 allstatus_.status().unsynced_count, 2095 allstatus_.status().conflicting_count, 2096 allstatus_.status().syncing, 2097 allstatus_.status().initial_sync_ended, 2098 allstatus_.status().syncer_stuck, 2099 allstatus_.status().updates_available, 2100 allstatus_.status().updates_received, 2101 allstatus_.status().disk_full, 2102 false, // TODO(ncarter): invalid store? 2103 allstatus_.status().max_consecutive_errors}; 2104 return return_status; 2105} 2106 2107void SyncManager::SyncInternal::OnSyncEngineEvent( 2108 const SyncEngineEvent& event) { 2109 if (!observer_) 2110 return; 2111 2112 // Only send an event if this is due to a cycle ending and this cycle 2113 // concludes a canonical "sync" process; that is, based on what is known 2114 // locally we are "all happy" and up-to-date. There may be new changes on 2115 // the server, but we'll get them on a subsequent sync. 2116 // 2117 // Notifications are sent at the end of every sync cycle, regardless of 2118 // whether we should sync again. 2119 if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) { 2120 ModelSafeRoutingInfo enabled_types; 2121 registrar_->GetModelSafeRoutingInfo(&enabled_types); 2122 if (enabled_types.count(syncable::PASSWORDS) > 0) { 2123 Cryptographer* cryptographer = 2124 GetUserShare()->dir_manager->cryptographer(); 2125 if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) { 2126 sync_api::ReadTransaction trans(GetUserShare()); 2127 sync_api::ReadNode node(&trans); 2128 if (!node.InitByTagLookup(kNigoriTag)) { 2129 DCHECK(!event.snapshot->is_share_usable); 2130 return; 2131 } 2132 const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics(); 2133 if (!nigori.encrypted().blob().empty()) { 2134 DCHECK(!cryptographer->CanDecrypt(nigori.encrypted())); 2135 cryptographer->SetPendingKeys(nigori.encrypted()); 2136 } 2137 } 2138 2139 // If we've completed a sync cycle and the cryptographer isn't ready yet, 2140 // prompt the user for a passphrase. 2141 if (cryptographer->has_pending_keys()) { 2142 observer_->OnPassphraseRequired(true); 2143 } else if (!cryptographer->is_ready()) { 2144 observer_->OnPassphraseRequired(false); 2145 } 2146 } 2147 2148 if (!initialized()) 2149 return; 2150 2151 if (!event.snapshot->has_more_to_sync) { 2152 observer_->OnSyncCycleCompleted(event.snapshot); 2153 } 2154 2155 if (notifier_options_.notification_method != 2156 notifier::NOTIFICATION_SERVER) { 2157 // TODO(chron): Consider changing this back to track has_more_to_sync 2158 // only notify peers if a successful commit has occurred. 2159 bool new_pending_notification = 2160 (event.snapshot->syncer_status.num_successful_commits > 0); 2161 core_message_loop_->PostTask( 2162 FROM_HERE, 2163 NewRunnableMethod( 2164 this, 2165 &SyncManager::SyncInternal::SendPendingXMPPNotification, 2166 new_pending_notification)); 2167 } 2168 } 2169 2170 if (event.what_happened == SyncEngineEvent::SYNCER_THREAD_PAUSED) { 2171 observer_->OnPaused(); 2172 return; 2173 } 2174 2175 if (event.what_happened == SyncEngineEvent::SYNCER_THREAD_RESUMED) { 2176 observer_->OnResumed(); 2177 return; 2178 } 2179 2180 if (event.what_happened == SyncEngineEvent::STOP_SYNCING_PERMANENTLY) { 2181 observer_->OnStopSyncingPermanently(); 2182 return; 2183 } 2184 2185 if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_SUCCEEDED) { 2186 observer_->OnClearServerDataSucceeded(); 2187 return; 2188 } 2189 2190 if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_FAILED) { 2191 observer_->OnClearServerDataFailed(); 2192 return; 2193 } 2194 2195 if (event.what_happened == SyncEngineEvent::UPDATED_TOKEN) { 2196 observer_->OnUpdatedToken(event.updated_token); 2197 return; 2198 } 2199} 2200 2201void SyncManager::SyncInternal::OnNotificationStateChange( 2202 bool notifications_enabled) { 2203 VLOG(1) << "P2P: Notifications enabled = " 2204 << (notifications_enabled ? "true" : "false"); 2205 allstatus_.SetNotificationsEnabled(notifications_enabled); 2206 if (syncer_thread()) { 2207 syncer_thread()->SetNotificationsEnabled(notifications_enabled); 2208 } 2209 if ((notifier_options_.notification_method != 2210 notifier::NOTIFICATION_SERVER) && notifications_enabled) { 2211 // Nudge the syncer thread when notifications are enabled, in case there is 2212 // any data that has not yet been synced. If we are listening to 2213 // server-issued notifications, we are already guaranteed to receive a 2214 // notification on a successful connection. 2215 if (syncer_thread()) { 2216 syncer_thread()->NudgeSyncer(0, SyncerThread::kLocal); 2217 } 2218 2219 // Send a notification as soon as subscriptions are on 2220 // (see http://code.google.com/p/chromium/issues/detail?id=38563 ). 2221 core_message_loop_->PostTask( 2222 FROM_HERE, 2223 NewRunnableMethod( 2224 this, 2225 &SyncManager::SyncInternal::SendPendingXMPPNotification, 2226 true)); 2227 } 2228} 2229 2230void SyncManager::SyncInternal::TalkMediatorLogin( 2231 const std::string& email, const std::string& token) { 2232 DCHECK_EQ(MessageLoop::current(), core_message_loop_); 2233 DCHECK(!email.empty()); 2234 DCHECK(!token.empty()); 2235 InitializeTalkMediator(); 2236 talk_mediator_->SetAuthToken(email, token, SYNC_SERVICE_NAME); 2237 talk_mediator_->Login(); 2238} 2239 2240void SyncManager::SyncInternal::OnIncomingNotification( 2241 const IncomingNotificationData& notification_data) { 2242 syncable::ModelTypeBitSet model_types; 2243 2244 // Check if the service url is a sync URL. An empty service URL is 2245 // treated as a legacy sync notification. If we're listening to 2246 // server-issued notifications, no need to check the service_url. 2247 if (notifier_options_.notification_method == 2248 notifier::NOTIFICATION_SERVER) { 2249 VLOG(1) << "Sync received server notification: " << 2250 notification_data.service_specific_data; 2251 2252 if (!syncable::ModelTypeBitSetFromString( 2253 notification_data.service_specific_data, 2254 &model_types)) { 2255 LOG(DFATAL) << "Could not extract model types from server data."; 2256 model_types.set(); 2257 } 2258 } else if (notification_data.service_url.empty() || 2259 (notification_data.service_url == 2260 browser_sync::kSyncLegacyServiceUrl) || 2261 (notification_data.service_url == 2262 browser_sync::kSyncServiceUrl)) { 2263 VLOG(1) << "Sync received P2P notification."; 2264 2265 // Catch for sync integration tests (uses p2p). Just set all datatypes. 2266 model_types.set(); 2267 } else { 2268 LOG(WARNING) << "Notification fron unexpected source: " 2269 << notification_data.service_url; 2270 } 2271 2272 if (model_types.any()) { 2273 if (syncer_thread()) { 2274 // Introduce a delay to help coalesce initial notifications. 2275 syncer_thread()->NudgeSyncerWithDataTypes( 2276 250, 2277 SyncerThread::kNotification, 2278 model_types); 2279 } 2280 allstatus_.IncrementNotificationsReceived(); 2281 } else { 2282 LOG(WARNING) << "Sync received notification without any type information."; 2283 } 2284} 2285 2286void SyncManager::SyncInternal::OnOutgoingNotification() { 2287 DCHECK_NE(notifier_options_.notification_method, 2288 notifier::NOTIFICATION_SERVER); 2289 allstatus_.IncrementNotificationsSent(); 2290} 2291 2292void SyncManager::SyncInternal::WriteState(const std::string& state) { 2293 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 2294 if (!lookup.good()) { 2295 LOG(ERROR) << "Could not write notification state"; 2296 // TODO(akalin): Propagate result callback all the way to this 2297 // function and call it with "false" to signal failure. 2298 return; 2299 } 2300 if (VLOG_IS_ON(1)) { 2301 std::string encoded_state; 2302 base::Base64Encode(state, &encoded_state); 2303 VLOG(1) << "Writing notification state: " << encoded_state; 2304 } 2305 lookup->SetNotificationState(state); 2306 lookup->SaveChanges(); 2307} 2308 2309SyncManager::Status::Summary SyncManager::GetStatusSummary() const { 2310 return data_->ComputeAggregatedStatusSummary(); 2311} 2312 2313SyncManager::Status SyncManager::GetDetailedStatus() const { 2314 return data_->ComputeAggregatedStatus(); 2315} 2316 2317SyncManager::SyncInternal* SyncManager::GetImpl() const { return data_; } 2318 2319void SyncManager::SaveChanges() { 2320 data_->SaveChanges(); 2321} 2322 2323void SyncManager::SyncInternal::SaveChanges() { 2324 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); 2325 if (!lookup.good()) { 2326 DCHECK(false) << "ScopedDirLookup creation failed; Unable to SaveChanges"; 2327 return; 2328 } 2329 lookup->SaveChanges(); 2330} 2331 2332////////////////////////////////////////////////////////////////////////// 2333// BaseTransaction member definitions 2334BaseTransaction::BaseTransaction(UserShare* share) 2335 : lookup_(NULL) { 2336 DCHECK(share && share->dir_manager.get()); 2337 lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(), 2338 share->name); 2339 cryptographer_ = share->dir_manager->cryptographer(); 2340 if (!(lookup_->good())) 2341 DCHECK(false) << "ScopedDirLookup failed on valid DirManager."; 2342} 2343BaseTransaction::~BaseTransaction() { 2344 delete lookup_; 2345} 2346 2347UserShare* SyncManager::GetUserShare() const { 2348 DCHECK(data_->initialized()) << "GetUserShare requires initialization!"; 2349 return data_->GetUserShare(); 2350} 2351 2352bool SyncManager::HasUnsyncedItems() const { 2353 sync_api::ReadTransaction trans(GetUserShare()); 2354 return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); 2355} 2356 2357} // namespace sync_api 2358