diff options
author | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 06:05:27 +0000 |
---|---|---|
committer | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 06:05:27 +0000 |
commit | 5852edc1b6eab234b9e048c41dd0d664ae7fc747 (patch) | |
tree | 9e5d8eb4833b76cdb11e66fc3607689e0f5e0122 /chrome/browser/sync/engine/all_status.cc | |
parent | f6059e37f8b8ac335ce18a189a13e702974a1c7e (diff) | |
download | chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.zip chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.tar.gz chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.tar.bz2 |
Initial commit of sync engine code to browser/sync.
The code is not built on any platform yet. That will arrive
as a subsequent checkin.
This is an implementation of the interface exposed earlier
through syncapi.h. It is the client side of a sync
protocol that lets users sync their browser data
(currently, just bookmarks) with their Google Account.
Table of contents:
browser/sync/
protocol - The protocol definition, and
other definitions necessary to connect to
the service.
syncable/ - defines a data model for syncable objects,
and provides a sqlite-based backing store
for this model.
engine/ - includes the core sync logic, including commiting
changes to the server, downloading changes from
the server, resolving conflicts, other parts of
the sync algorithm.
engine/net - parts of the sync engine focused on the
business of talking to the server. Some of
this is binds a generic "server connection"
interface to a concrete implementation
provided by Chromium.
notifier - the part of the syncer focused on the business
of sending and receiving xmpp notifications.
Notifications are used instead of polling to
achieve very low latency change propagation.
util - not necessarily sync specific utility code. Much
of this is scaffolding which should either be
replaced by, or merged with, the utility code
in base/.
BUG=none
TEST=this code includes its own suite of unit tests.
Review URL: http://codereview.chromium.org/194065
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25850 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/engine/all_status.cc')
-rw-r--r-- | chrome/browser/sync/engine/all_status.cc | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/all_status.cc b/chrome/browser/sync/engine/all_status.cc new file mode 100644 index 0000000..e1bc5c7 --- /dev/null +++ b/chrome/browser/sync/engine/all_status.cc @@ -0,0 +1,335 @@ +// 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/engine/syncproto.h" +#include "chrome/browser/sync/notifier/listener/talk_mediator.h" +#include "chrome/browser/sync/protocol/service_constants.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 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 >= 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() : channel_(new Channel(shutdown_event)), + status_(init_status) { + 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->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(); + SyncerStatus syncerStatus(event.last_session); + status.unsynced_count += syncerStatus.unsynced_count(); + status.conflicting_count += syncerStatus.conflicting_commits(); + if (syncerStatus.current_sync_timestamp() == + syncerStatus.servers_latest_timestamp()) { + status.conflicting_count += syncerStatus.conflicting_updates(); + } + status.syncing |= syncerStatus.syncing(); + // Show a syncer as syncing if it's got stalled updates. + status.syncing = event.last_session->ShouldSyncAgain(); + status.initial_sync_ended |= syncerStatus.IsShareUsable(); + status.syncer_stuck |= syncerStatus.syncer_stuck(); + if (syncerStatus.consecutive_errors() > status.max_consecutive_errors) + status.max_consecutive_errors = syncerStatus.consecutive_errors(); + + // 100 is an arbitrary limit. + if (syncerStatus.consecutive_transient_error_commits() > 100) + status.server_broken = true; + + status.updates_available += syncerStatus.servers_latest_timestamp(); + status.updates_received += syncerStatus.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::SYNC_CYCLE_ENDED: + case SyncerEvent::COMMITS_SUCCEEDED: + break; + 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 { + MutexLock 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_.Lock(); + event_.status = allstatus->status_; +} + +ScopedStatusLockWithNotify::~ScopedStatusLockWithNotify() { + if (DONT_NOTIFY == plan_) { + allstatus_->mutex_.Unlock(); + return; + } + event_.what_changed |= allstatus_->CalcStatusChanges(&event_.status); + allstatus_->mutex_.Unlock(); + if (event_.what_changed) + allstatus_->channel()->NotifyListeners(event_); +} + +void ScopedStatusLockWithNotify::NotifyOverQuota() { + event_.what_changed |= AllStatusEvent::OVER_QUOTA; +} + +} // namespace browser_sync |