profile_sync_service_harness.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
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 "base/message_loop.h" 6#include "chrome/browser/defaults.h" 7#include "chrome/browser/prefs/pref_service.h" 8#include "chrome/browser/profile.h" 9#include "chrome/browser/net/gaia/token_service.h" 10#include "chrome/browser/sync/glue/sync_backend_host.h" 11#include "chrome/browser/sync/profile_sync_service_harness.h" 12#include "chrome/browser/sync/sessions/session_state.h" 13#include "chrome/browser/tab_contents/tab_contents.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/common/net/gaia/gaia_constants.h" 16#include "chrome/common/net/gaia/google_service_auth_error.h" 17#include "chrome/common/notification_source.h" 18#include "chrome/common/pref_names.h" 19 20// The default value for min_timestamp_needed_ when we're not in the 21// WAITING_FOR_UPDATES state. 22static const int kMinTimestampNeededNone = -1; 23 24// The amount of time for which we wait for a live sync operation to complete. 25static const int kLiveSyncOperationTimeoutMs = 30000; 26 27// Simple class to implement a timeout using PostDelayedTask. If it is not 28// aborted before picked up by a message queue, then it asserts with the message 29// provided. This class is not thread safe. 30class StateChangeTimeoutEvent 31 : public base::RefCountedThreadSafe<StateChangeTimeoutEvent> { 32 public: 33 StateChangeTimeoutEvent(ProfileSyncServiceHarness* caller, 34 const std::string& message); 35 36 // The entry point to the class from PostDelayedTask. 37 void Callback(); 38 39 // Cancels the actions of the callback. Returns true if success, false 40 // if the callback has already timed out. 41 bool Abort(); 42 43 private: 44 friend class base::RefCountedThreadSafe<StateChangeTimeoutEvent>; 45 46 ~StateChangeTimeoutEvent(); 47 48 bool aborted_; 49 bool did_timeout_; 50 51 // Due to synchronization of the IO loop, the caller will always be alive 52 // if the class is not aborted. 53 ProfileSyncServiceHarness* caller_; 54 55 // Informative message to assert in the case of a timeout. 56 std::string message_; 57 58 DISALLOW_COPY_AND_ASSIGN(StateChangeTimeoutEvent); 59}; 60 61StateChangeTimeoutEvent::StateChangeTimeoutEvent( 62 ProfileSyncServiceHarness* caller, 63 const std::string& message) 64 : aborted_(false), did_timeout_(false), caller_(caller), message_(message) { 65} 66 67StateChangeTimeoutEvent::~StateChangeTimeoutEvent() { 68} 69 70void StateChangeTimeoutEvent::Callback() { 71 if (!aborted_) { 72 if (!caller_->RunStateChangeMachine()) { 73 // Report the message. 74 did_timeout_ = true; 75 DCHECK(!aborted_) << message_; 76 caller_->SignalStateComplete(); 77 } 78 } 79} 80 81bool StateChangeTimeoutEvent::Abort() { 82 aborted_ = true; 83 caller_ = NULL; 84 return !did_timeout_; 85} 86 87ProfileSyncServiceHarness::ProfileSyncServiceHarness( 88 Profile* profile, 89 const std::string& username, 90 const std::string& password, 91 int id) 92 : wait_state_(INITIAL_WAIT_STATE), 93 profile_(profile), 94 service_(NULL), 95 last_timestamp_(0), 96 min_timestamp_needed_(kMinTimestampNeededNone), 97 username_(username), 98 password_(password), 99 passphrase_acceptance_counter_(0), 100 id_(id) { 101 if (IsSyncAlreadySetup()) { 102 service_ = profile_->GetProfileSyncService(); 103 service_->AddObserver(this); 104 wait_state_ = FULLY_SYNCED; 105 } 106} 107 108// static 109ProfileSyncServiceHarness* ProfileSyncServiceHarness::CreateAndAttach( 110 Profile* profile) { 111 if (!profile->HasProfileSyncService()) { 112 NOTREACHED() << "Profile has never signed into sync."; 113 return NULL; 114 } 115 return new ProfileSyncServiceHarness(profile, "", "", 0); 116} 117 118void ProfileSyncServiceHarness::SetCredentials(const std::string& username, 119 const std::string& password) { 120 username_ = username; 121 password_ = password; 122} 123 124bool ProfileSyncServiceHarness::IsSyncAlreadySetup() { 125 return profile_->HasProfileSyncService(); 126} 127 128bool ProfileSyncServiceHarness::SetupSync() { 129 syncable::ModelTypeSet synced_datatypes; 130 for (int i = syncable::FIRST_REAL_MODEL_TYPE; 131 i < syncable::MODEL_TYPE_COUNT; ++i) { 132 synced_datatypes.insert(syncable::ModelTypeFromInt(i)); 133 } 134 return SetupSync(synced_datatypes); 135} 136 137void ProfileSyncServiceHarness::StartObservingPassphraseEvents() { 138 // Prime the counter to account for the implicit set passphrase due to 139 // gaia login. 140 passphrase_acceptance_counter_--; 141 registrar_.Add(this, NotificationType::SYNC_PASSPHRASE_ACCEPTED, 142 Source<browser_sync::SyncBackendHost>(service_->backend())); 143 registrar_.Add(this, NotificationType::SYNC_PASSPHRASE_REQUIRED, 144 Source<browser_sync::SyncBackendHost>(service_->backend())); 145} 146 147bool ProfileSyncServiceHarness::SetupSync( 148 const syncable::ModelTypeSet& synced_datatypes) { 149 // Initialize the sync client's profile sync service object. 150 service_ = profile_->GetProfileSyncService(); 151 if (service_ == NULL) { 152 LOG(ERROR) << "SetupSync(): service_ is null."; 153 return false; 154 } 155 156 // Subscribe sync client to notifications from the profile sync service. 157 if (!service_->HasObserver(this)) 158 service_->AddObserver(this); 159 160 // Authenticate sync client using GAIA credentials. 161 service_->signin()->StartSignIn(username_, password_, "", ""); 162 163 // Wait for the OnBackendInitialized() callback. 164 wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED; 165 if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, 166 "Waiting for OnBackendInitialized().")) { 167 LOG(ERROR) << "OnBackendInitialized() not seen after " 168 << kLiveSyncOperationTimeoutMs / 1000 169 << " seconds."; 170 return false; 171 } 172 173 // Choose the datatypes to be synced. If all datatypes are to be synced, 174 // set sync_everything to true; otherwise, set it to false. 175 bool sync_everything = (synced_datatypes.size() == 176 (syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE)); 177 service()->OnUserChoseDatatypes(sync_everything, synced_datatypes); 178 179 // Wait for a passphrase to be required. 180 DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_REQUIRED); 181 if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, 182 "Waiting for Passphrase required.")) { 183 LOG(ERROR) << "Passphrase required not seen after " 184 << kLiveSyncOperationTimeoutMs / 1000 185 << " seconds."; 186 return false; 187 } 188 189 DCHECK(service()->observed_passphrase_required()); 190 if (id_ == 0) 191 DCHECK(!service()->passphrase_required_for_decryption()); 192 193 // Wait for initial gaia passphrase to be accepted. 194 DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_ACCEPTED); 195 if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, 196 "Waiting for Passphrase accept.")) { 197 LOG(ERROR) << "Passphrase accept not seen after " 198 << kLiveSyncOperationTimeoutMs / 1000 199 << " seconds."; 200 return false; 201 } 202 203 DCHECK_EQ(wait_state_, WAITING_FOR_INITIAL_SYNC); 204 // Wait for initial sync cycle to be completed. 205 if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, 206 "Waiting for initial sync cycle to complete.")) { 207 LOG(ERROR) << "Initial sync cycle did not complete after " 208 << kLiveSyncOperationTimeoutMs / 1000 209 << " seconds."; 210 return false; 211 } 212 213 // Indicate to the browser that sync setup is complete. 214 service()->SetSyncSetupCompleted(); 215 216 return true; 217} 218 219void ProfileSyncServiceHarness::SignalStateCompleteWithNextState( 220 WaitState next_state) { 221 wait_state_ = next_state; 222 SignalStateComplete(); 223} 224 225void ProfileSyncServiceHarness::SignalStateComplete() { 226 MessageLoop::current()->Quit(); 227} 228 229bool ProfileSyncServiceHarness::RunStateChangeMachine() { 230 WaitState original_wait_state = wait_state_; 231 switch (wait_state_) { 232 case WAITING_FOR_ON_BACKEND_INITIALIZED: { 233 LogClientInfo("WAITING_FOR_ON_BACKEND_INITIALIZED"); 234 if (service()->sync_initialized()) { 235 // The sync backend is initialized. Watch for passphrase events. 236 StartObservingPassphraseEvents(); 237 SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_REQUIRED); 238 } 239 break; 240 } 241 case WAITING_FOR_PASSPHRASE_REQUIRED: { 242 LogClientInfo("WAITING_FOR_PASSPHRASE_REQUIRED"); 243 if (service_->observed_passphrase_required()) 244 SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_ACCEPTED); 245 break; 246 } 247 case WAITING_FOR_PASSPHRASE_ACCEPTED: { 248 LogClientInfo("WAITING_FOR_PASSPHRASE_ACCEPTED"); 249 if (passphrase_acceptance_counter_ >= 0) 250 SignalStateCompleteWithNextState(WAITING_FOR_INITIAL_SYNC); 251 break; 252 } 253 case WAITING_FOR_INITIAL_SYNC: { 254 LogClientInfo("WAITING_FOR_INITIAL_SYNC"); 255 if (IsSynced()) { 256 // The first sync cycle is now complete. We can start running tests. 257 SignalStateCompleteWithNextState(FULLY_SYNCED); 258 } 259 break; 260 } 261 case WAITING_FOR_SYNC_TO_FINISH: { 262 LogClientInfo("WAITING_FOR_SYNC_TO_FINISH"); 263 if (!IsSynced()) { 264 // The client is not yet fully synced. Continue waiting. 265 if (!GetStatus().server_reachable) { 266 // The client cannot reach the sync server because the network is 267 // disabled. There is no need to wait anymore. 268 SignalStateCompleteWithNextState(SERVER_UNREACHABLE); 269 } 270 break; 271 } 272 GetUpdatedTimestamp(); 273 SignalStateCompleteWithNextState(FULLY_SYNCED); 274 break; 275 } 276 case WAITING_FOR_UPDATES: { 277 LogClientInfo("WAITING_FOR_UPDATES"); 278 if (!IsSynced() || GetUpdatedTimestamp() < min_timestamp_needed_) { 279 // The client is not yet fully synced. Continue waiting until the client 280 // is at the required minimum timestamp. 281 break; 282 } 283 SignalStateCompleteWithNextState(FULLY_SYNCED); 284 break; 285 } 286 case SERVER_UNREACHABLE: { 287 LogClientInfo("SERVER_UNREACHABLE"); 288 if (GetStatus().server_reachable) { 289 // The client was offline due to the network being disabled, but is now 290 // back online. Wait for the pending sync cycle to complete. 291 SignalStateCompleteWithNextState(WAITING_FOR_SYNC_TO_FINISH); 292 } 293 break; 294 } 295 case FULLY_SYNCED: { 296 // The client is online and fully synced. There is nothing to do. 297 LogClientInfo("FULLY_SYNCED"); 298 break; 299 } 300 case SYNC_DISABLED: { 301 // Syncing is disabled for the client. There is nothing to do. 302 LogClientInfo("SYNC_DISABLED"); 303 break; 304 } 305 default: 306 // Invalid state during observer callback which may be triggered by other 307 // classes using the the UI message loop. Defer to their handling. 308 break; 309 } 310 return original_wait_state != wait_state_; 311} 312 313void ProfileSyncServiceHarness::OnStateChanged() { 314 RunStateChangeMachine(); 315} 316 317bool ProfileSyncServiceHarness::AwaitPassphraseAccepted() { 318 LogClientInfo("AwaitPassphraseAccepted"); 319 if (wait_state_ == SYNC_DISABLED) { 320 LOG(ERROR) << "Sync disabled for Client " << id_ << "."; 321 return false; 322 } 323 passphrase_acceptance_counter_--; 324 if (passphrase_acceptance_counter_ >= 0) 325 return true; 326 wait_state_ = WAITING_FOR_PASSPHRASE_ACCEPTED; 327 return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, 328 "Waiting for passphrase accepted."); 329} 330 331void ProfileSyncServiceHarness::Observe(NotificationType type, 332 const NotificationSource& source, 333 const NotificationDetails& details) { 334 if (NotificationType::SYNC_PASSPHRASE_ACCEPTED == type.value) { 335 passphrase_acceptance_counter_++; 336 RunStateChangeMachine(); 337 } else if (NotificationType::SYNC_PASSPHRASE_REQUIRED == type.value) { 338 RunStateChangeMachine(); 339 } 340} 341 342bool ProfileSyncServiceHarness::AwaitSyncCycleCompletion( 343 const std::string& reason) { 344 LogClientInfo("AwaitSyncCycleCompletion"); 345 if (wait_state_ == SYNC_DISABLED) { 346 LOG(ERROR) << "Sync disabled for Client " << id_ << "."; 347 return false; 348 } 349 if (!IsSynced()) { 350 if (wait_state_ == SERVER_UNREACHABLE) { 351 // Client was offline; wait for it to go online, and then wait for sync. 352 AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); 353 DCHECK_EQ(wait_state_, WAITING_FOR_SYNC_TO_FINISH); 354 return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); 355 } else { 356 DCHECK(service()->sync_initialized()); 357 wait_state_ = WAITING_FOR_SYNC_TO_FINISH; 358 AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); 359 if (wait_state_ == FULLY_SYNCED) { 360 // Client is online; sync was successful. 361 return true; 362 } else if (wait_state_ == SERVER_UNREACHABLE){ 363 // Client is offline; sync was unsuccessful. 364 return false; 365 } else { 366 LOG(ERROR) << "Invalid wait state:" << wait_state_; 367 return false; 368 } 369 } 370 } else { 371 // Client is already synced; don't wait. 372 GetUpdatedTimestamp(); 373 return true; 374 } 375} 376 377bool ProfileSyncServiceHarness::AwaitMutualSyncCycleCompletion( 378 ProfileSyncServiceHarness* partner) { 379 LogClientInfo("AwaitMutualSyncCycleCompletion"); 380 if (!AwaitSyncCycleCompletion("Sync cycle completion on active client.")) 381 return false; 382 return partner->WaitUntilTimestampIsAtLeast(last_timestamp_, 383 "Sync cycle completion on passive client."); 384} 385 386bool ProfileSyncServiceHarness::AwaitGroupSyncCycleCompletion( 387 std::vector<ProfileSyncServiceHarness*>& partners) { 388 LogClientInfo("AwaitGroupSyncCycleCompletion"); 389 if (!AwaitSyncCycleCompletion("Sync cycle completion on active client.")) 390 return false; 391 bool return_value = true; 392 for (std::vector<ProfileSyncServiceHarness*>::iterator it = 393 partners.begin(); it != partners.end(); ++it) { 394 if ((this != *it) && ((*it)->wait_state_ != SYNC_DISABLED)) { 395 return_value = return_value && 396 (*it)->WaitUntilTimestampIsAtLeast(last_timestamp_, 397 "Sync cycle completion on partner client."); 398 } 399 } 400 return return_value; 401} 402 403// static 404bool ProfileSyncServiceHarness::AwaitQuiescence( 405 std::vector<ProfileSyncServiceHarness*>& clients) { 406 VLOG(1) << "AwaitQuiescence."; 407 bool return_value = true; 408 for (std::vector<ProfileSyncServiceHarness*>::iterator it = 409 clients.begin(); it != clients.end(); ++it) { 410 if ((*it)->wait_state_ != SYNC_DISABLED) 411 return_value = return_value && 412 (*it)->AwaitGroupSyncCycleCompletion(clients); 413 } 414 return return_value; 415} 416 417bool ProfileSyncServiceHarness::WaitUntilTimestampIsAtLeast( 418 int64 timestamp, const std::string& reason) { 419 LogClientInfo("WaitUntilTimestampIsAtLeast"); 420 if (wait_state_ == SYNC_DISABLED) { 421 LOG(ERROR) << "Sync disabled for Client " << id_ << "."; 422 return false; 423 } 424 min_timestamp_needed_ = timestamp; 425 if (GetUpdatedTimestamp() < min_timestamp_needed_) { 426 wait_state_ = WAITING_FOR_UPDATES; 427 return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); 428 } else { 429 return true; 430 } 431} 432 433bool ProfileSyncServiceHarness::AwaitStatusChangeWithTimeout( 434 int timeout_milliseconds, 435 const std::string& reason) { 436 LogClientInfo("AwaitStatusChangeWithTimeout"); 437 if (wait_state_ == SYNC_DISABLED) { 438 LOG(ERROR) << "Sync disabled for Client " << id_ << "."; 439 return false; 440 } 441 scoped_refptr<StateChangeTimeoutEvent> timeout_signal( 442 new StateChangeTimeoutEvent(this, reason)); 443 MessageLoop* loop = MessageLoop::current(); 444 bool did_allow_nestable_tasks = loop->NestableTasksAllowed(); 445 loop->SetNestableTasksAllowed(true); 446 loop->PostDelayedTask( 447 FROM_HERE, 448 NewRunnableMethod(timeout_signal.get(), 449 &StateChangeTimeoutEvent::Callback), 450 timeout_milliseconds); 451 loop->Run(); 452 loop->SetNestableTasksAllowed(did_allow_nestable_tasks); 453 LogClientInfo("AwaitStatusChangeWithTimeout succeeded"); 454 return timeout_signal->Abort(); 455} 456 457ProfileSyncService::Status ProfileSyncServiceHarness::GetStatus() { 458 DCHECK(service() != NULL) << "GetStatus(): service() is NULL."; 459 return service()->QueryDetailedSyncStatus(); 460} 461 462bool ProfileSyncServiceHarness::IsSynced() { 463 LogClientInfo("IsSynced"); 464 if (service() == NULL) 465 return false; 466 const SyncSessionSnapshot* snap = GetLastSessionSnapshot(); 467 // TODO(rsimha): Remove additional checks of snap->has_more_to_sync and 468 // snap->unsynced_count once http://crbug.com/48989 is fixed. 469 return (snap && 470 snap->num_conflicting_updates == 0 && // We can decrypt everything. 471 ServiceIsPushingChanges() && 472 GetStatus().notifications_enabled && 473 !service()->backend()->HasUnsyncedItems() && 474 !snap->has_more_to_sync && 475 snap->unsynced_count == 0); 476} 477 478const SyncSessionSnapshot* 479 ProfileSyncServiceHarness::GetLastSessionSnapshot() const { 480 DCHECK(service_ != NULL) << "Sync service has not yet been set up."; 481 if (service_->backend()) { 482 return service_->backend()->GetLastSessionSnapshot(); 483 } 484 return NULL; 485} 486 487void ProfileSyncServiceHarness::EnableSyncForDatatype( 488 syncable::ModelType datatype) { 489 LogClientInfo("EnableSyncForDatatype"); 490 syncable::ModelTypeSet synced_datatypes; 491 if (wait_state_ == SYNC_DISABLED) { 492 wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED; 493 synced_datatypes.insert(datatype); 494 DCHECK(SetupSync(synced_datatypes)) << "Reinitialization of Client " << id_ 495 << " failed."; 496 } else { 497 DCHECK(service() != NULL) << "EnableSyncForDatatype(): service() is null."; 498 service()->GetPreferredDataTypes(&synced_datatypes); 499 syncable::ModelTypeSet::iterator it = synced_datatypes.find( 500 syncable::ModelTypeFromInt(datatype)); 501 if (it == synced_datatypes.end()) { 502 synced_datatypes.insert(syncable::ModelTypeFromInt(datatype)); 503 service()->OnUserChoseDatatypes(false, synced_datatypes); 504 wait_state_ = WAITING_FOR_SYNC_TO_FINISH; 505 AwaitSyncCycleCompletion("Waiting for datatype configuration."); 506 VLOG(1) << "EnableSyncForDatatype(): Enabled sync for datatype " 507 << syncable::ModelTypeToString(datatype) << " on Client " << id_; 508 } else { 509 VLOG(1) << "EnableSyncForDatatype(): Sync already enabled for datatype " 510 << syncable::ModelTypeToString(datatype) << " on Client " << id_; 511 } 512 } 513} 514 515void ProfileSyncServiceHarness::DisableSyncForDatatype( 516 syncable::ModelType datatype) { 517 LogClientInfo("DisableSyncForDatatype"); 518 syncable::ModelTypeSet synced_datatypes; 519 DCHECK(service() != NULL) << "DisableSyncForDatatype(): service() is null."; 520 service()->GetPreferredDataTypes(&synced_datatypes); 521 syncable::ModelTypeSet::iterator it = synced_datatypes.find(datatype); 522 if (it != synced_datatypes.end()) { 523 synced_datatypes.erase(it); 524 service()->OnUserChoseDatatypes(false, synced_datatypes); 525 AwaitSyncCycleCompletion("Waiting for datatype configuration."); 526 VLOG(1) << "DisableSyncForDatatype(): Disabled sync for datatype " 527 << syncable::ModelTypeToString(datatype) << " on Client " << id_; 528 } else { 529 VLOG(1) << "DisableSyncForDatatype(): Sync already disabled for datatype " 530 << syncable::ModelTypeToString(datatype) << " on Client " << id_; 531 } 532} 533 534void ProfileSyncServiceHarness::EnableSyncForAllDatatypes() { 535 LogClientInfo("EnableSyncForAllDatatypes"); 536 if (wait_state_ == SYNC_DISABLED) { 537 wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED; 538 DCHECK(SetupSync()) << "Reinitialization of Client " << id_ << " failed."; 539 } else { 540 syncable::ModelTypeSet synced_datatypes; 541 for (int i = syncable::FIRST_REAL_MODEL_TYPE; 542 i < syncable::MODEL_TYPE_COUNT; ++i) { 543 synced_datatypes.insert(syncable::ModelTypeFromInt(i)); 544 } 545 DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is " 546 " null."; 547 service()->OnUserChoseDatatypes(true, synced_datatypes); 548 wait_state_ = WAITING_FOR_SYNC_TO_FINISH; 549 AwaitSyncCycleCompletion("Waiting for datatype configuration."); 550 VLOG(1) << "EnableSyncForAllDatatypes(): Enabled sync for all datatypes on " 551 "Client " << id_; 552 } 553} 554 555void ProfileSyncServiceHarness::DisableSyncForAllDatatypes() { 556 LogClientInfo("DisableSyncForAllDatatypes"); 557 DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is " 558 "null."; 559 service()->DisableForUser(); 560 wait_state_ = SYNC_DISABLED; 561 VLOG(1) << "DisableSyncForAllDatatypes(): Disabled sync for all datatypes on " 562 "Client " << id_; 563} 564 565int64 ProfileSyncServiceHarness::GetUpdatedTimestamp() { 566 const SyncSessionSnapshot* snap = GetLastSessionSnapshot(); 567 DCHECK(snap != NULL) << "GetUpdatedTimestamp(): Sync snapshot is NULL."; 568 DCHECK_LE(last_timestamp_, snap->max_local_timestamp); 569 last_timestamp_ = snap->max_local_timestamp; 570 return last_timestamp_; 571} 572 573void ProfileSyncServiceHarness::LogClientInfo(std::string message) { 574 if (service()) { 575 const SyncSessionSnapshot* snap = GetLastSessionSnapshot(); 576 if (snap) { 577 VLOG(1) << "Client " << id_ << ": " << message 578 << ": max_local_timestamp: " << snap->max_local_timestamp 579 << ", has_more_to_sync: " << snap->has_more_to_sync 580 << ", unsynced_count: " << snap->unsynced_count 581 << ", num_conflicting_updates: " << snap->num_conflicting_updates 582 << ", has_unsynced_items: " 583 << service()->backend()->HasUnsyncedItems() 584 << ", observed_passphrase_required: " 585 << service()->observed_passphrase_required() 586 << ", notifications_enabled: " 587 << GetStatus().notifications_enabled 588 << ", service_is_pushing_changes: " << ServiceIsPushingChanges(); 589 } else { 590 VLOG(1) << "Client " << id_ << ": " << message 591 << ": Sync session snapshot not available."; 592 } 593 } else { 594 VLOG(1) << "Client " << id_ << ": " << message 595 << ": Sync service not available."; 596 } 597} 598