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