summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/engine/all_status.cc
diff options
context:
space:
mode:
authornick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-10 06:05:27 +0000
committernick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-10 06:05:27 +0000
commit5852edc1b6eab234b9e048c41dd0d664ae7fc747 (patch)
tree9e5d8eb4833b76cdb11e66fc3607689e0f5e0122 /chrome/browser/sync/engine/all_status.cc
parentf6059e37f8b8ac335ce18a189a13e702974a1c7e (diff)
downloadchromium_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.cc335
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