blob: 49ec3ceccaa590e157f8b6ec08b14e0389f2d39e [file] [log] [blame]
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sync/engine/all_status.h"
#include <algorithm>
#include "base/logging.h"
#include "base/port.h"
#include "base/rand_util.h"
#include "chrome/browser/sync/engine/auth_watcher.h"
#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
#include "chrome/browser/sync/engine/net/server_connection_manager.h"
#include "chrome/browser/sync/engine/syncer.h"
#include "chrome/browser/sync/engine/syncer_thread.h"
#include "chrome/browser/sync/notifier/listener/talk_mediator.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/sessions/session_state.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/util/event_sys-inl.h"
namespace browser_sync {
static const time_t kMinSyncObserveInterval = 10; // seconds
// Backoff interval randomization factor.
static const int kBackoffRandomizationFactor = 2;
const int AllStatus::kMaxBackoffSeconds = 60 * 60 * 4; // 4 hours.
const char* AllStatus::GetSyncStatusString(SyncStatus icon) {
const char* strings[] = {"OFFLINE", "OFFLINE_UNSYNCED", "SYNCING", "READY",
"CONFLICT", "OFFLINE_UNUSABLE"};
COMPILE_ASSERT(arraysize(strings) == ICON_STATUS_COUNT, enum_indexed_array);
if (icon < 0 || icon >= static_cast<SyncStatus>(arraysize(strings)))
LOG(FATAL) << "Illegal Icon State:" << icon;
return strings[icon];
}
static const AllStatus::Status init_status =
{ AllStatus::OFFLINE };
static const AllStatusEvent shutdown_event =
{ AllStatusEvent::SHUTDOWN, init_status };
AllStatus::AllStatus() : status_(init_status),
channel_(new Channel(shutdown_event)) {
status_.initial_sync_ended = true;
status_.notifications_enabled = false;
}
AllStatus::~AllStatus() {
delete channel_;
}
void AllStatus::WatchConnectionManager(ServerConnectionManager* conn_mgr) {
conn_mgr_hookup_.reset(NewEventListenerHookup(conn_mgr->channel(), this,
&AllStatus::HandleServerConnectionEvent));
}
void AllStatus::WatchAuthenticator(GaiaAuthenticator* gaia) {
gaia_hookup_.reset(NewEventListenerHookup(gaia->channel(), this,
&AllStatus::HandleGaiaAuthEvent));
}
void AllStatus::WatchAuthWatcher(AuthWatcher* auth_watcher) {
authwatcher_hookup_.reset(
NewEventListenerHookup(auth_watcher->channel(), this,
&AllStatus::HandleAuthWatcherEvent));
}
void AllStatus::WatchSyncerThread(SyncerThread* syncer_thread) {
syncer_thread_hookup_.reset(
NewEventListenerHookup(syncer_thread->relay_channel(), this,
&AllStatus::HandleSyncerEvent));
}
AllStatus::Status AllStatus::CreateBlankStatus() const {
Status status = status_;
status.syncing = true;
status.unsynced_count = 0;
status.conflicting_count = 0;
status.initial_sync_ended = false;
status.syncer_stuck = false;
status.max_consecutive_errors = 0;
status.server_broken = false;
status.updates_available = 0;
status.updates_received = 0;
return status;
}
AllStatus::Status AllStatus::CalcSyncing(const SyncerEvent &event) const {
Status status = CreateBlankStatus();
const sessions::SyncSessionSnapshot* snapshot = event.snapshot;
status.unsynced_count += static_cast<int>(snapshot->unsynced_count);
status.conflicting_count += snapshot->errors.num_conflicting_commits;
// The syncer may not be done yet, which could cause conflicting updates.
// But this is only used for status, so it is better to have visibility.
status.conflicting_count += snapshot->num_conflicting_updates;
status.syncing |= snapshot->syncer_status.syncing;
status.syncing = snapshot->has_more_to_sync && snapshot->is_silenced;
status.initial_sync_ended |= snapshot->is_share_usable;
status.syncer_stuck |= snapshot->syncer_status.syncer_stuck;
const sessions::ErrorCounters& errors(snapshot->errors);
if (errors.consecutive_errors > status.max_consecutive_errors)
status.max_consecutive_errors = errors.consecutive_errors;
// 100 is an arbitrary limit.
if (errors.consecutive_transient_error_commits > 100)
status.server_broken = true;
const sessions::ChangelogProgress& progress(snapshot->changelog_progress);
status.updates_available += progress.num_server_changes_remaining;
status.updates_received += progress.current_sync_timestamp;
return status;
}
AllStatus::Status AllStatus::CalcSyncing() const {
return CreateBlankStatus();
}
int AllStatus::CalcStatusChanges(Status* old_status) {
int what_changed = 0;
// Calculate what changed and what the new icon should be.
if (status_.syncing != old_status->syncing)
what_changed |= AllStatusEvent::SYNCING;
if (status_.unsynced_count != old_status->unsynced_count)
what_changed |= AllStatusEvent::UNSYNCED_COUNT;
if (status_.server_up != old_status->server_up)
what_changed |= AllStatusEvent::SERVER_UP;
if (status_.server_reachable != old_status->server_reachable)
what_changed |= AllStatusEvent::SERVER_REACHABLE;
if (status_.notifications_enabled != old_status->notifications_enabled)
what_changed |= AllStatusEvent::NOTIFICATIONS_ENABLED;
if (status_.notifications_received != old_status->notifications_received)
what_changed |= AllStatusEvent::NOTIFICATIONS_RECEIVED;
if (status_.notifications_sent != old_status->notifications_sent)
what_changed |= AllStatusEvent::NOTIFICATIONS_SENT;
if (status_.initial_sync_ended != old_status->initial_sync_ended)
what_changed |= AllStatusEvent::INITIAL_SYNC_ENDED;
if (status_.authenticated != old_status->authenticated)
what_changed |= AllStatusEvent::AUTHENTICATED;
const bool unsynced_changes = status_.unsynced_count > 0;
const bool online = status_.authenticated &&
status_.server_reachable && status_.server_up && !status_.server_broken;
if (online) {
if (status_.syncer_stuck)
status_.icon = CONFLICT;
else if (unsynced_changes || status_.syncing)
status_.icon = SYNCING;
else
status_.icon = READY;
} else if (!status_.initial_sync_ended) {
status_.icon = OFFLINE_UNUSABLE;
} else if (unsynced_changes) {
status_.icon = OFFLINE_UNSYNCED;
} else {
status_.icon = OFFLINE;
}
if (status_.icon != old_status->icon)
what_changed |= AllStatusEvent::ICON;
if (0 == what_changed)
return 0;
*old_status = status_;
return what_changed;
}
void AllStatus::HandleGaiaAuthEvent(const GaiaAuthEvent& gaia_event) {
ScopedStatusLockWithNotify lock(this);
switch (gaia_event.what_happened) {
case GaiaAuthEvent::GAIA_AUTH_FAILED:
status_.authenticated = false;
break;
case GaiaAuthEvent::GAIA_AUTH_SUCCEEDED:
status_.authenticated = true;
break;
default:
lock.set_notify_plan(DONT_NOTIFY);
break;
}
}
void AllStatus::HandleAuthWatcherEvent(const AuthWatcherEvent& auth_event) {
ScopedStatusLockWithNotify lock(this);
switch (auth_event.what_happened) {
case AuthWatcherEvent::GAIA_AUTH_FAILED:
case AuthWatcherEvent::SERVICE_AUTH_FAILED:
case AuthWatcherEvent::SERVICE_CONNECTION_FAILED:
case AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START:
status_.authenticated = false;
break;
case AuthWatcherEvent::AUTH_SUCCEEDED:
// If we've already calculated that the server is reachable, since we've
// successfully authenticated, we can be confident that the server is up.
if (status_.server_reachable)
status_.server_up = true;
if (!status_.authenticated) {
status_.authenticated = true;
status_ = CalcSyncing();
} else {
lock.set_notify_plan(DONT_NOTIFY);
}
break;
default:
lock.set_notify_plan(DONT_NOTIFY);
break;
}
}
void AllStatus::HandleSyncerEvent(const SyncerEvent& event) {
ScopedStatusLockWithNotify lock(this);
switch (event.what_happened) {
case SyncerEvent::COMMITS_SUCCEEDED:
break;
case SyncerEvent::SYNC_CYCLE_ENDED:
case SyncerEvent::STATUS_CHANGED:
status_ = CalcSyncing(event);
break;
case SyncerEvent::SHUTDOWN_USE_WITH_CARE:
// We're safe to use this value here because we don't call into the syncer
// or block on any processes.
lock.set_notify_plan(DONT_NOTIFY);
break;
case SyncerEvent::OVER_QUOTA:
LOG(WARNING) << "User has gone over quota.";
lock.NotifyOverQuota();
break;
case SyncerEvent::REQUEST_SYNC_NUDGE:
lock.set_notify_plan(DONT_NOTIFY);
break;
default:
LOG(ERROR) << "Unrecognized Syncer Event: " << event.what_happened;
lock.set_notify_plan(DONT_NOTIFY);
break;
}
}
void AllStatus::HandleServerConnectionEvent(
const ServerConnectionEvent& event) {
if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) {
ScopedStatusLockWithNotify lock(this);
status_.server_up = IsGoodReplyFromServer(event.connection_code);
status_.server_reachable = event.server_reachable;
}
}
void AllStatus::WatchTalkMediator(const TalkMediator* mediator) {
status_.notifications_enabled = false;
talk_mediator_hookup_.reset(
NewEventListenerHookup(mediator->channel(), this,
&AllStatus::HandleTalkMediatorEvent));
}
void AllStatus::HandleTalkMediatorEvent(
const TalkMediatorEvent& event) {
ScopedStatusLockWithNotify lock(this);
switch (event.what_happened) {
case TalkMediatorEvent::SUBSCRIPTIONS_ON:
status_.notifications_enabled = true;
break;
case TalkMediatorEvent::LOGOUT_SUCCEEDED:
case TalkMediatorEvent::SUBSCRIPTIONS_OFF:
case TalkMediatorEvent::TALKMEDIATOR_DESTROYED:
status_.notifications_enabled = false;
break;
case TalkMediatorEvent::NOTIFICATION_RECEIVED:
status_.notifications_received++;
break;
case TalkMediatorEvent::NOTIFICATION_SENT:
status_.notifications_sent++;
break;
case TalkMediatorEvent::LOGIN_SUCCEEDED:
default:
lock.set_notify_plan(DONT_NOTIFY);
break;
}
}
AllStatus::Status AllStatus::status() const {
AutoLock lock(mutex_);
return status_;
}
int AllStatus::GetRecommendedDelaySeconds(int base_delay_seconds) {
if (base_delay_seconds >= kMaxBackoffSeconds)
return kMaxBackoffSeconds;
// This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2
int backoff_s = (0 == base_delay_seconds) ? 1 :
base_delay_seconds * kBackoffRandomizationFactor;
// Flip a coin to randomize backoff interval by +/- 50%.
int rand_sign = base::RandInt(0, 1) * 2 - 1;
// Truncation is adequate for rounding here.
backoff_s = backoff_s +
(rand_sign * (base_delay_seconds / kBackoffRandomizationFactor));
// Cap the backoff interval.
backoff_s = std::min(backoff_s, kMaxBackoffSeconds);
return backoff_s;
}
int AllStatus::GetRecommendedDelay(int base_delay_ms) const {
return GetRecommendedDelaySeconds(base_delay_ms / 1000) * 1000;
}
ScopedStatusLockWithNotify::ScopedStatusLockWithNotify(AllStatus* allstatus)
: allstatus_(allstatus), plan_(NOTIFY_IF_STATUS_CHANGED) {
event_.what_changed = 0;
allstatus->mutex_.Acquire();
event_.status = allstatus->status_;
}
ScopedStatusLockWithNotify::~ScopedStatusLockWithNotify() {
if (DONT_NOTIFY == plan_) {
allstatus_->mutex_.Release();
return;
}
event_.what_changed |= allstatus_->CalcStatusChanges(&event_.status);
allstatus_->mutex_.Release();
if (event_.what_changed)
allstatus_->channel()->NotifyListeners(event_);
}
void ScopedStatusLockWithNotify::NotifyOverQuota() {
event_.what_changed |= AllStatusEvent::OVER_QUOTA;
}
} // namespace browser_sync