all_status.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2006-2009 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/all_status.h" 6 7#include <algorithm> 8 9#include "base/logging.h" 10#include "base/port.h" 11#include "base/rand_util.h" 12#include "chrome/browser/sync/engine/auth_watcher.h" 13#include "chrome/browser/sync/engine/net/server_connection_manager.h" 14#include "chrome/browser/sync/engine/syncer.h" 15#include "chrome/browser/sync/engine/syncer_thread.h" 16#include "chrome/browser/sync/protocol/service_constants.h" 17#include "chrome/browser/sync/sessions/session_state.h" 18#include "chrome/browser/sync/syncable/directory_manager.h" 19#include "chrome/common/deprecated/event_sys-inl.h" 20#include "jingle/notifier/listener/talk_mediator.h" 21 22namespace browser_sync { 23 24static const time_t kMinSyncObserveInterval = 10; // seconds 25 26// Backoff interval randomization factor. 27static const int kBackoffRandomizationFactor = 2; 28 29const int AllStatus::kMaxBackoffSeconds = 60 * 60 * 4; // 4 hours. 30 31const char* AllStatus::GetSyncStatusString(SyncStatus icon) { 32 const char* strings[] = {"OFFLINE", "OFFLINE_UNSYNCED", "SYNCING", "READY", 33 "CONFLICT", "OFFLINE_UNUSABLE"}; 34 COMPILE_ASSERT(arraysize(strings) == ICON_STATUS_COUNT, enum_indexed_array); 35 if (icon < 0 || icon >= static_cast<SyncStatus>(arraysize(strings))) 36 LOG(FATAL) << "Illegal Icon State:" << icon; 37 return strings[icon]; 38} 39 40static const AllStatus::Status init_status = 41 { AllStatus::OFFLINE }; 42 43static const AllStatusEvent shutdown_event = 44 { AllStatusEvent::SHUTDOWN, init_status }; 45 46AllStatus::AllStatus() : status_(init_status), 47 channel_(new Channel(shutdown_event)) { 48 status_.initial_sync_ended = true; 49 status_.notifications_enabled = false; 50} 51 52AllStatus::~AllStatus() { 53 syncer_thread_hookup_.reset(); 54 delete channel_; 55} 56 57void AllStatus::WatchConnectionManager(ServerConnectionManager* conn_mgr) { 58 conn_mgr_hookup_.reset(NewEventListenerHookup(conn_mgr->channel(), this, 59 &AllStatus::HandleServerConnectionEvent)); 60} 61 62void AllStatus::WatchSyncerThread(SyncerThread* syncer_thread) { 63 syncer_thread_hookup_.reset(syncer_thread == NULL ? NULL : 64 syncer_thread->relay_channel()->AddObserver(this)); 65} 66 67AllStatus::Status AllStatus::CreateBlankStatus() const { 68 Status status = status_; 69 status.syncing = true; 70 status.unsynced_count = 0; 71 status.conflicting_count = 0; 72 status.initial_sync_ended = false; 73 status.syncer_stuck = false; 74 status.max_consecutive_errors = 0; 75 status.server_broken = false; 76 status.updates_available = 0; 77 status.updates_received = 0; 78 return status; 79} 80 81AllStatus::Status AllStatus::CalcSyncing(const SyncerEvent &event) const { 82 Status status = CreateBlankStatus(); 83 const sessions::SyncSessionSnapshot* snapshot = event.snapshot; 84 status.unsynced_count += static_cast<int>(snapshot->unsynced_count); 85 status.conflicting_count += snapshot->errors.num_conflicting_commits; 86 // The syncer may not be done yet, which could cause conflicting updates. 87 // But this is only used for status, so it is better to have visibility. 88 status.conflicting_count += snapshot->num_conflicting_updates; 89 90 status.syncing |= snapshot->syncer_status.syncing; 91 status.syncing = snapshot->has_more_to_sync && snapshot->is_silenced; 92 status.initial_sync_ended |= snapshot->is_share_usable; 93 status.syncer_stuck |= snapshot->syncer_status.syncer_stuck; 94 95 const sessions::ErrorCounters& errors(snapshot->errors); 96 if (errors.consecutive_errors > status.max_consecutive_errors) 97 status.max_consecutive_errors = errors.consecutive_errors; 98 99 // 100 is an arbitrary limit. 100 if (errors.consecutive_transient_error_commits > 100) 101 status.server_broken = true; 102 103 status.updates_available += snapshot->num_server_changes_remaining; 104 status.updates_received += snapshot->max_local_timestamp; 105 return status; 106} 107 108AllStatus::Status AllStatus::CalcSyncing() const { 109 return CreateBlankStatus(); 110} 111 112int AllStatus::CalcStatusChanges(Status* old_status) { 113 int what_changed = 0; 114 115 // Calculate what changed and what the new icon should be. 116 if (status_.syncing != old_status->syncing) 117 what_changed |= AllStatusEvent::SYNCING; 118 if (status_.unsynced_count != old_status->unsynced_count) 119 what_changed |= AllStatusEvent::UNSYNCED_COUNT; 120 if (status_.server_up != old_status->server_up) 121 what_changed |= AllStatusEvent::SERVER_UP; 122 if (status_.server_reachable != old_status->server_reachable) 123 what_changed |= AllStatusEvent::SERVER_REACHABLE; 124 if (status_.notifications_enabled != old_status->notifications_enabled) 125 what_changed |= AllStatusEvent::NOTIFICATIONS_ENABLED; 126 if (status_.notifications_received != old_status->notifications_received) 127 what_changed |= AllStatusEvent::NOTIFICATIONS_RECEIVED; 128 if (status_.notifications_sent != old_status->notifications_sent) 129 what_changed |= AllStatusEvent::NOTIFICATIONS_SENT; 130 if (status_.initial_sync_ended != old_status->initial_sync_ended) 131 what_changed |= AllStatusEvent::INITIAL_SYNC_ENDED; 132 if (status_.authenticated != old_status->authenticated) 133 what_changed |= AllStatusEvent::AUTHENTICATED; 134 135 const bool unsynced_changes = status_.unsynced_count > 0; 136 const bool online = status_.authenticated && 137 status_.server_reachable && status_.server_up && !status_.server_broken; 138 if (online) { 139 if (status_.syncer_stuck) 140 status_.icon = CONFLICT; 141 else if (unsynced_changes || status_.syncing) 142 status_.icon = SYNCING; 143 else 144 status_.icon = READY; 145 } else if (!status_.initial_sync_ended) { 146 status_.icon = OFFLINE_UNUSABLE; 147 } else if (unsynced_changes) { 148 status_.icon = OFFLINE_UNSYNCED; 149 } else { 150 status_.icon = OFFLINE; 151 } 152 153 if (status_.icon != old_status->icon) 154 what_changed |= AllStatusEvent::ICON; 155 156 if (0 == what_changed) 157 return 0; 158 *old_status = status_; 159 return what_changed; 160} 161 162void AllStatus::HandleAuthWatcherEvent(const AuthWatcherEvent& auth_event) { 163 ScopedStatusLockWithNotify lock(this); 164 switch (auth_event.what_happened) { 165 case AuthWatcherEvent::GAIA_AUTH_FAILED: 166 case AuthWatcherEvent::SERVICE_AUTH_FAILED: 167 case AuthWatcherEvent::SERVICE_CONNECTION_FAILED: 168 case AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START: 169 status_.authenticated = false; 170 break; 171 case AuthWatcherEvent::AUTH_SUCCEEDED: 172 // If we've already calculated that the server is reachable, since we've 173 // successfully authenticated, we can be confident that the server is up. 174 if (status_.server_reachable) 175 status_.server_up = true; 176 177 if (!status_.authenticated) { 178 status_.authenticated = true; 179 status_ = CalcSyncing(); 180 } else { 181 lock.set_notify_plan(DONT_NOTIFY); 182 } 183 break; 184 default: 185 lock.set_notify_plan(DONT_NOTIFY); 186 break; 187 } 188} 189 190void AllStatus::HandleChannelEvent(const SyncerEvent& event) { 191 ScopedStatusLockWithNotify lock(this); 192 switch (event.what_happened) { 193 case SyncerEvent::COMMITS_SUCCEEDED: 194 break; 195 case SyncerEvent::SYNC_CYCLE_ENDED: 196 case SyncerEvent::STATUS_CHANGED: 197 status_ = CalcSyncing(event); 198 break; 199 case SyncerEvent::SHUTDOWN_USE_WITH_CARE: 200 // We're safe to use this value here because we don't call into the syncer 201 // or block on any processes. 202 lock.set_notify_plan(DONT_NOTIFY); 203 syncer_thread_hookup_.reset(); 204 break; 205 case SyncerEvent::OVER_QUOTA: 206 LOG(WARNING) << "User has gone over quota."; 207 lock.NotifyOverQuota(); 208 break; 209 case SyncerEvent::REQUEST_SYNC_NUDGE: 210 case SyncerEvent::PAUSED: 211 case SyncerEvent::RESUMED: 212 case SyncerEvent::WAITING_FOR_CONNECTION: 213 case SyncerEvent::CONNECTED: 214 case SyncerEvent::STOP_SYNCING_PERMANENTLY: 215 case SyncerEvent::SYNCER_THREAD_EXITING: 216 lock.set_notify_plan(DONT_NOTIFY); 217 break; 218 default: 219 LOG(ERROR) << "Unrecognized Syncer Event: " << event.what_happened; 220 lock.set_notify_plan(DONT_NOTIFY); 221 break; 222 } 223} 224 225void AllStatus::HandleServerConnectionEvent( 226 const ServerConnectionEvent& event) { 227 if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) { 228 ScopedStatusLockWithNotify lock(this); 229 status_.server_up = IsGoodReplyFromServer(event.connection_code); 230 status_.server_reachable = event.server_reachable; 231 } 232} 233 234AllStatus::Status AllStatus::status() const { 235 AutoLock lock(mutex_); 236 return status_; 237} 238 239int AllStatus::GetRecommendedDelaySeconds(int base_delay_seconds) { 240 if (base_delay_seconds >= kMaxBackoffSeconds) 241 return kMaxBackoffSeconds; 242 243 // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2 244 int backoff_s = (0 == base_delay_seconds) ? 1 : 245 base_delay_seconds * kBackoffRandomizationFactor; 246 247 // Flip a coin to randomize backoff interval by +/- 50%. 248 int rand_sign = base::RandInt(0, 1) * 2 - 1; 249 250 // Truncation is adequate for rounding here. 251 backoff_s = backoff_s + 252 (rand_sign * (base_delay_seconds / kBackoffRandomizationFactor)); 253 254 // Cap the backoff interval. 255 backoff_s = std::min(backoff_s, kMaxBackoffSeconds); 256 257 return backoff_s; 258} 259 260int AllStatus::GetRecommendedDelay(int base_delay_ms) const { 261 return GetRecommendedDelaySeconds(base_delay_ms / 1000) * 1000; 262} 263 264void AllStatus::SetNotificationsEnabled(bool notifications_enabled) { 265 ScopedStatusLockWithNotify lock(this); 266 status_.notifications_enabled = notifications_enabled; 267} 268 269void AllStatus::IncrementNotificationsSent() { 270 ScopedStatusLockWithNotify lock(this); 271 ++status_.notifications_sent; 272} 273 274void AllStatus::IncrementNotificationsReceived() { 275 ScopedStatusLockWithNotify lock(this); 276 ++status_.notifications_received; 277} 278 279ScopedStatusLockWithNotify::ScopedStatusLockWithNotify(AllStatus* allstatus) 280 : allstatus_(allstatus), plan_(NOTIFY_IF_STATUS_CHANGED) { 281 event_.what_changed = 0; 282 allstatus->mutex_.Acquire(); 283 event_.status = allstatus->status_; 284} 285 286ScopedStatusLockWithNotify::~ScopedStatusLockWithNotify() { 287 if (DONT_NOTIFY == plan_) { 288 allstatus_->mutex_.Release(); 289 return; 290 } 291 event_.what_changed |= allstatus_->CalcStatusChanges(&event_.status); 292 allstatus_->mutex_.Release(); 293 if (event_.what_changed) 294 allstatus_->channel()->NotifyListeners(event_); 295} 296 297void ScopedStatusLockWithNotify::NotifyOverQuota() { 298 event_.what_changed |= AllStatusEvent::OVER_QUOTA; 299} 300 301} // namespace browser_sync 302