OLD | NEW |
(Empty) | |
| 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/gaia_authenticator.h" |
| 14 #include "chrome/browser/sync/engine/net/server_connection_manager.h" |
| 15 #include "chrome/browser/sync/engine/syncer.h" |
| 16 #include "chrome/browser/sync/engine/syncer_thread.h" |
| 17 #include "chrome/browser/sync/engine/syncproto.h" |
| 18 #include "chrome/browser/sync/notifier/listener/talk_mediator.h" |
| 19 #include "chrome/browser/sync/protocol/service_constants.h" |
| 20 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 21 #include "chrome/browser/sync/util/event_sys-inl.h" |
| 22 |
| 23 namespace browser_sync { |
| 24 |
| 25 static const time_t kMinSyncObserveInterval = 10; // seconds |
| 26 |
| 27 // Backoff interval randomization factor. |
| 28 static const int kBackoffRandomizationFactor = 2; |
| 29 |
| 30 const char* AllStatus::GetSyncStatusString(SyncStatus icon) { |
| 31 const char* strings[] = {"OFFLINE", "OFFLINE_UNSYNCED", "SYNCING", "READY", |
| 32 "CONFLICT", "OFFLINE_UNUSABLE"}; |
| 33 COMPILE_ASSERT(ARRAYSIZE(strings) == ICON_STATUS_COUNT, enum_indexed_array); |
| 34 if (icon < 0 || icon >= ARRAYSIZE(strings)) |
| 35 LOG(FATAL) << "Illegal Icon State:" << icon; |
| 36 return strings[icon]; |
| 37 } |
| 38 |
| 39 static const AllStatus::Status init_status = |
| 40 { AllStatus::OFFLINE }; |
| 41 |
| 42 static const AllStatusEvent shutdown_event = |
| 43 { AllStatusEvent::SHUTDOWN, init_status }; |
| 44 |
| 45 AllStatus::AllStatus() : channel_(new Channel(shutdown_event)), |
| 46 status_(init_status) { |
| 47 status_.initial_sync_ended = true; |
| 48 status_.notifications_enabled = false; |
| 49 } |
| 50 |
| 51 AllStatus::~AllStatus() { |
| 52 delete channel_; |
| 53 } |
| 54 |
| 55 void AllStatus::WatchConnectionManager(ServerConnectionManager* conn_mgr) { |
| 56 conn_mgr_hookup_.reset(NewEventListenerHookup(conn_mgr->channel(), this, |
| 57 &AllStatus::HandleServerConnectionEvent)); |
| 58 } |
| 59 |
| 60 void AllStatus::WatchAuthenticator(GaiaAuthenticator* gaia) { |
| 61 gaia_hookup_.reset(NewEventListenerHookup(gaia->channel(), this, |
| 62 &AllStatus::HandleGaiaAuthEvent)); |
| 63 } |
| 64 |
| 65 void AllStatus::WatchAuthWatcher(AuthWatcher* auth_watcher) { |
| 66 authwatcher_hookup_.reset( |
| 67 NewEventListenerHookup(auth_watcher->channel(), this, |
| 68 &AllStatus::HandleAuthWatcherEvent)); |
| 69 } |
| 70 |
| 71 void AllStatus::WatchSyncerThread(SyncerThread* syncer_thread) { |
| 72 syncer_thread_hookup_.reset( |
| 73 NewEventListenerHookup(syncer_thread->channel(), this, |
| 74 &AllStatus::HandleSyncerEvent)); |
| 75 } |
| 76 |
| 77 AllStatus::Status AllStatus::CreateBlankStatus() const { |
| 78 Status status = status_; |
| 79 status.syncing = true; |
| 80 status.unsynced_count = 0; |
| 81 status.conflicting_count = 0; |
| 82 status.initial_sync_ended = false; |
| 83 status.syncer_stuck = false; |
| 84 status.max_consecutive_errors = 0; |
| 85 status.server_broken = false; |
| 86 status.updates_available = 0; |
| 87 status.updates_received = 0; |
| 88 return status; |
| 89 } |
| 90 |
| 91 AllStatus::Status AllStatus::CalcSyncing(const SyncerEvent &event) const { |
| 92 Status status = CreateBlankStatus(); |
| 93 SyncerStatus syncerStatus(event.last_session); |
| 94 status.unsynced_count += syncerStatus.unsynced_count(); |
| 95 status.conflicting_count += syncerStatus.conflicting_commits(); |
| 96 if (syncerStatus.current_sync_timestamp() == |
| 97 syncerStatus.servers_latest_timestamp()) { |
| 98 status.conflicting_count += syncerStatus.conflicting_updates(); |
| 99 } |
| 100 status.syncing |= syncerStatus.syncing(); |
| 101 // Show a syncer as syncing if it's got stalled updates. |
| 102 status.syncing = event.last_session->ShouldSyncAgain(); |
| 103 status.initial_sync_ended |= syncerStatus.IsShareUsable(); |
| 104 status.syncer_stuck |= syncerStatus.syncer_stuck(); |
| 105 if (syncerStatus.consecutive_errors() > status.max_consecutive_errors) |
| 106 status.max_consecutive_errors = syncerStatus.consecutive_errors(); |
| 107 |
| 108 // 100 is an arbitrary limit. |
| 109 if (syncerStatus.consecutive_transient_error_commits() > 100) |
| 110 status.server_broken = true; |
| 111 |
| 112 status.updates_available += syncerStatus.servers_latest_timestamp(); |
| 113 status.updates_received += syncerStatus.current_sync_timestamp(); |
| 114 return status; |
| 115 } |
| 116 |
| 117 AllStatus::Status AllStatus::CalcSyncing() const { |
| 118 return CreateBlankStatus(); |
| 119 } |
| 120 |
| 121 int AllStatus::CalcStatusChanges(Status* old_status) { |
| 122 int what_changed = 0; |
| 123 |
| 124 // Calculate what changed and what the new icon should be. |
| 125 if (status_.syncing != old_status->syncing) |
| 126 what_changed |= AllStatusEvent::SYNCING; |
| 127 if (status_.unsynced_count != old_status->unsynced_count) |
| 128 what_changed |= AllStatusEvent::UNSYNCED_COUNT; |
| 129 if (status_.server_up != old_status->server_up) |
| 130 what_changed |= AllStatusEvent::SERVER_UP; |
| 131 if (status_.server_reachable != old_status->server_reachable) |
| 132 what_changed |= AllStatusEvent::SERVER_REACHABLE; |
| 133 if (status_.notifications_enabled != old_status->notifications_enabled) |
| 134 what_changed |= AllStatusEvent::NOTIFICATIONS_ENABLED; |
| 135 if (status_.notifications_received != old_status->notifications_received) |
| 136 what_changed |= AllStatusEvent::NOTIFICATIONS_RECEIVED; |
| 137 if (status_.notifications_sent != old_status->notifications_sent) |
| 138 what_changed |= AllStatusEvent::NOTIFICATIONS_SENT; |
| 139 if (status_.initial_sync_ended != old_status->initial_sync_ended) |
| 140 what_changed |= AllStatusEvent::INITIAL_SYNC_ENDED; |
| 141 if (status_.authenticated != old_status->authenticated) |
| 142 what_changed |= AllStatusEvent::AUTHENTICATED; |
| 143 |
| 144 const bool unsynced_changes = status_.unsynced_count > 0; |
| 145 const bool online = status_.authenticated && |
| 146 status_.server_reachable && status_.server_up && !status_.server_broken; |
| 147 if (online) { |
| 148 if (status_.syncer_stuck) |
| 149 status_.icon = CONFLICT; |
| 150 else if (unsynced_changes || status_.syncing) |
| 151 status_.icon = SYNCING; |
| 152 else |
| 153 status_.icon = READY; |
| 154 } else if (!status_.initial_sync_ended) { |
| 155 status_.icon = OFFLINE_UNUSABLE; |
| 156 } else if (unsynced_changes) { |
| 157 status_.icon = OFFLINE_UNSYNCED; |
| 158 } else { |
| 159 status_.icon = OFFLINE; |
| 160 } |
| 161 |
| 162 if (status_.icon != old_status->icon) |
| 163 what_changed |= AllStatusEvent::ICON; |
| 164 |
| 165 if (0 == what_changed) |
| 166 return 0; |
| 167 *old_status = status_; |
| 168 return what_changed; |
| 169 } |
| 170 |
| 171 void AllStatus::HandleGaiaAuthEvent(const GaiaAuthEvent& gaia_event) { |
| 172 ScopedStatusLockWithNotify lock(this); |
| 173 switch (gaia_event.what_happened) { |
| 174 case GaiaAuthEvent::GAIA_AUTH_FAILED: |
| 175 status_.authenticated = false; |
| 176 break; |
| 177 case GaiaAuthEvent::GAIA_AUTH_SUCCEEDED: |
| 178 status_.authenticated = true; |
| 179 break; |
| 180 default: |
| 181 lock.set_notify_plan(DONT_NOTIFY); |
| 182 break; |
| 183 } |
| 184 } |
| 185 |
| 186 void AllStatus::HandleAuthWatcherEvent(const AuthWatcherEvent& auth_event) { |
| 187 ScopedStatusLockWithNotify lock(this); |
| 188 switch (auth_event.what_happened) { |
| 189 case AuthWatcherEvent::GAIA_AUTH_FAILED: |
| 190 case AuthWatcherEvent::SERVICE_AUTH_FAILED: |
| 191 case AuthWatcherEvent::SERVICE_CONNECTION_FAILED: |
| 192 case AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START: |
| 193 status_.authenticated = false; |
| 194 break; |
| 195 case AuthWatcherEvent::AUTH_SUCCEEDED: |
| 196 // If we've already calculated that the server is reachable, since we've |
| 197 // successfully authenticated, we can be confident that the server is up. |
| 198 if (status_.server_reachable) |
| 199 status_.server_up = true; |
| 200 |
| 201 if (!status_.authenticated) { |
| 202 status_.authenticated = true; |
| 203 status_ = CalcSyncing(); |
| 204 } else { |
| 205 lock.set_notify_plan(DONT_NOTIFY); |
| 206 } |
| 207 break; |
| 208 default: |
| 209 lock.set_notify_plan(DONT_NOTIFY); |
| 210 break; |
| 211 } |
| 212 } |
| 213 |
| 214 void AllStatus::HandleSyncerEvent(const SyncerEvent& event) { |
| 215 ScopedStatusLockWithNotify lock(this); |
| 216 switch (event.what_happened) { |
| 217 case SyncerEvent::SYNC_CYCLE_ENDED: |
| 218 case SyncerEvent::COMMITS_SUCCEEDED: |
| 219 break; |
| 220 case SyncerEvent::STATUS_CHANGED: |
| 221 status_ = CalcSyncing(event); |
| 222 break; |
| 223 case SyncerEvent::SHUTDOWN_USE_WITH_CARE: |
| 224 // We're safe to use this value here because we don't call into the syncer |
| 225 // or block on any processes. |
| 226 lock.set_notify_plan(DONT_NOTIFY); |
| 227 break; |
| 228 case SyncerEvent::OVER_QUOTA: |
| 229 LOG(WARNING) << "User has gone over quota."; |
| 230 lock.NotifyOverQuota(); |
| 231 break; |
| 232 case SyncerEvent::REQUEST_SYNC_NUDGE: |
| 233 lock.set_notify_plan(DONT_NOTIFY); |
| 234 break; |
| 235 default: |
| 236 LOG(ERROR) << "Unrecognized Syncer Event: " << event.what_happened; |
| 237 lock.set_notify_plan(DONT_NOTIFY); |
| 238 break; |
| 239 } |
| 240 } |
| 241 |
| 242 void AllStatus::HandleServerConnectionEvent( |
| 243 const ServerConnectionEvent& event) { |
| 244 if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) { |
| 245 ScopedStatusLockWithNotify lock(this); |
| 246 status_.server_up = IsGoodReplyFromServer(event.connection_code); |
| 247 status_.server_reachable = event.server_reachable; |
| 248 } |
| 249 } |
| 250 |
| 251 void AllStatus::WatchTalkMediator(const TalkMediator* mediator) { |
| 252 status_.notifications_enabled = false; |
| 253 talk_mediator_hookup_.reset( |
| 254 NewEventListenerHookup(mediator->channel(), this, |
| 255 &AllStatus::HandleTalkMediatorEvent)); |
| 256 } |
| 257 |
| 258 void AllStatus::HandleTalkMediatorEvent( |
| 259 const TalkMediatorEvent& event) { |
| 260 ScopedStatusLockWithNotify lock(this); |
| 261 switch (event.what_happened) { |
| 262 case TalkMediatorEvent::SUBSCRIPTIONS_ON: |
| 263 status_.notifications_enabled = true; |
| 264 break; |
| 265 case TalkMediatorEvent::LOGOUT_SUCCEEDED: |
| 266 case TalkMediatorEvent::SUBSCRIPTIONS_OFF: |
| 267 case TalkMediatorEvent::TALKMEDIATOR_DESTROYED: |
| 268 status_.notifications_enabled = false; |
| 269 break; |
| 270 case TalkMediatorEvent::NOTIFICATION_RECEIVED: |
| 271 status_.notifications_received++; |
| 272 break; |
| 273 case TalkMediatorEvent::NOTIFICATION_SENT: |
| 274 status_.notifications_sent++; |
| 275 break; |
| 276 case TalkMediatorEvent::LOGIN_SUCCEEDED: |
| 277 default: |
| 278 lock.set_notify_plan(DONT_NOTIFY); |
| 279 break; |
| 280 } |
| 281 } |
| 282 |
| 283 AllStatus::Status AllStatus::status() const { |
| 284 MutexLock lock(&mutex_); |
| 285 return status_; |
| 286 } |
| 287 |
| 288 int AllStatus::GetRecommendedDelaySeconds(int base_delay_seconds) { |
| 289 if (base_delay_seconds >= kMaxBackoffSeconds) |
| 290 return kMaxBackoffSeconds; |
| 291 |
| 292 // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2 |
| 293 int backoff_s = (0 == base_delay_seconds) ? 1 : |
| 294 base_delay_seconds * kBackoffRandomizationFactor; |
| 295 |
| 296 // Flip a coin to randomize backoff interval by +/- 50%. |
| 297 int rand_sign = base::RandInt(0, 1) * 2 - 1; |
| 298 |
| 299 // Truncation is adequate for rounding here. |
| 300 backoff_s = backoff_s + |
| 301 (rand_sign * (base_delay_seconds / kBackoffRandomizationFactor)); |
| 302 |
| 303 // Cap the backoff interval. |
| 304 backoff_s = std::min(backoff_s, kMaxBackoffSeconds); |
| 305 |
| 306 return backoff_s; |
| 307 } |
| 308 |
| 309 int AllStatus::GetRecommendedDelay(int base_delay_ms) const { |
| 310 return GetRecommendedDelaySeconds(base_delay_ms / 1000) * 1000; |
| 311 } |
| 312 |
| 313 ScopedStatusLockWithNotify::ScopedStatusLockWithNotify(AllStatus* allstatus) |
| 314 : allstatus_(allstatus), plan_(NOTIFY_IF_STATUS_CHANGED) { |
| 315 event_.what_changed = 0; |
| 316 allstatus->mutex_.Lock(); |
| 317 event_.status = allstatus->status_; |
| 318 } |
| 319 |
| 320 ScopedStatusLockWithNotify::~ScopedStatusLockWithNotify() { |
| 321 if (DONT_NOTIFY == plan_) { |
| 322 allstatus_->mutex_.Unlock(); |
| 323 return; |
| 324 } |
| 325 event_.what_changed |= allstatus_->CalcStatusChanges(&event_.status); |
| 326 allstatus_->mutex_.Unlock(); |
| 327 if (event_.what_changed) |
| 328 allstatus_->channel()->NotifyListeners(event_); |
| 329 } |
| 330 |
| 331 void ScopedStatusLockWithNotify::NotifyOverQuota() { |
| 332 event_.what_changed |= AllStatusEvent::OVER_QUOTA; |
| 333 } |
| 334 |
| 335 } // namespace browser_sync |
OLD | NEW |