1// Copyright (c) 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// StatusController handles all counter and status related number crunching and
6// state tracking on behalf of a SyncSession.  It 'controls' the model data
7// defined in session_state.h.  The most important feature of StatusController
8// is the ScopedModelSafetyRestriction. When one of these is active, the
9// underlying data set exposed via accessors is swapped out to the appropriate
10// set for the restricted ModelSafeGroup behind the scenes.  For example, if
11// GROUP_UI is set, then accessors such as conflict_progress() and commit_ids()
12// are implicitly restricted to returning only data pertaining to GROUP_UI.
13// You can see which parts of status fall into this "restricted" category, or
14// the global "shared" category for all model types, by looking at the struct
15// declarations in session_state.h. If these accessors are invoked without a
16// restriction in place, this is a violation and will cause debug assertions
17// to surface improper use of the API in development.  Likewise for
18// invocation of "shared" accessors when a restriction is in place; for
19// safety's sake, an assertion will fire.
20//
21// NOTE: There is no concurrent access protection provided by this class. It
22// assumes one single thread is accessing this class for each unique
23// ModelSafeGroup, and also only one single thread (in practice, the
24// SyncerThread) responsible for all "shared" access when no restriction is in
25// place. Thus, every bit of data is to be accessed mutually exclusively with
26// respect to threads.
27//
28// StatusController can also track if changes occur to certain parts of state
29// so that various parts of the sync engine can avoid broadcasting
30// notifications if no changes occurred.
31
32#ifndef CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_
33#define CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_
34#pragma once
35
36#include <map>
37
38#include "base/stl_util-inl.h"
39#include "chrome/browser/sync/sessions/ordered_commit_set.h"
40#include "chrome/browser/sync/sessions/session_state.h"
41
42namespace browser_sync {
43namespace sessions {
44
45class StatusController {
46 public:
47  explicit StatusController(const ModelSafeRoutingInfo& routes);
48  ~StatusController();
49
50  // Returns true if some portion of the session state has changed (is dirty)
51  // since it was created or was last reset.
52  bool TestAndClearIsDirty();
53
54  // Progress counters.
55  const ConflictProgress& conflict_progress() {
56    return GetOrCreateModelSafeGroupState(true,
57        group_restriction_)->conflict_progress;
58  }
59  ConflictProgress* mutable_conflict_progress() {
60    return &GetOrCreateModelSafeGroupState(true,
61        group_restriction_)->conflict_progress;
62  }
63  const UpdateProgress& update_progress() {
64    return GetOrCreateModelSafeGroupState(true,
65        group_restriction_)->update_progress;
66  }
67  UpdateProgress* mutable_update_progress() {
68    return &GetOrCreateModelSafeGroupState(true,
69        group_restriction_)->update_progress;
70  }
71  // Some unrestricted, non-ModelChangingSyncerCommand commands need to store
72  // meta information about updates.
73  UpdateProgress* GetUnrestrictedUpdateProgress(ModelSafeGroup group) {
74    return &GetOrCreateModelSafeGroupState(false, group)->update_progress;
75  }
76
77  // ClientToServer messages.
78  const ClientToServerMessage& commit_message() {
79    return shared_.commit_message;
80  }
81  ClientToServerMessage* mutable_commit_message() {
82    return &shared_.commit_message;
83  }
84  const ClientToServerResponse& commit_response() const {
85    return shared_.commit_response;
86  }
87  ClientToServerResponse* mutable_commit_response() {
88    return &shared_.commit_response;
89  }
90  const syncable::ModelTypeBitSet& updates_request_types() const {
91    return shared_.updates_request_types;
92  }
93  void set_updates_request_types(const syncable::ModelTypeBitSet& value) {
94    shared_.updates_request_types = value;
95  }
96  const ClientToServerResponse& updates_response() const {
97    return shared_.updates_response;
98  }
99  ClientToServerResponse* mutable_updates_response() {
100    return &shared_.updates_response;
101  }
102
103  // Errors and SyncerStatus.
104  const ErrorCounters& error_counters() const {
105    return shared_.error_counters.value();
106  }
107  const SyncerStatus& syncer_status() const {
108    return shared_.syncer_status.value();
109  }
110
111  // Changelog related state.
112  int64 num_server_changes_remaining() const {
113    return shared_.num_server_changes_remaining.value();
114  }
115
116  // Commit path data.
117  const std::vector<syncable::Id>& commit_ids() const {
118    DCHECK(!group_restriction_in_effect_) << "Group restriction in effect!";
119    return shared_.commit_set.GetAllCommitIds();
120  }
121  const OrderedCommitSet::Projection& commit_id_projection() {
122    DCHECK(group_restriction_in_effect_)
123        << "No group restriction for projection.";
124    return shared_.commit_set.GetCommitIdProjection(group_restriction_);
125  }
126  const syncable::Id& GetCommitIdAt(size_t index) {
127    DCHECK(CurrentCommitIdProjectionHasIndex(index));
128    return shared_.commit_set.GetCommitIdAt(index);
129  }
130  syncable::ModelType GetCommitIdModelTypeAt(size_t index) {
131    DCHECK(CurrentCommitIdProjectionHasIndex(index));
132    return shared_.commit_set.GetModelTypeAt(index);
133  }
134  const std::vector<int64>& unsynced_handles() const {
135    DCHECK(!group_restriction_in_effect_)
136        << "unsynced_handles is unrestricted.";
137    return shared_.unsynced_handles.value();
138  }
139
140  // Control parameters for sync cycles.
141  bool conflict_sets_built() const {
142    return shared_.control_params.conflict_sets_built;
143  }
144  bool conflicts_resolved() const {
145    return shared_.control_params.conflicts_resolved;
146  }
147  bool did_commit_items() const {
148    return shared_.control_params.items_committed;
149  }
150
151  // If a GetUpdates for any data type resulted in downloading an update that
152  // is in conflict, this method returns true.
153  bool HasConflictingUpdates() const;
154
155  // Aggregate sum of ConflictingItemSize() over all ConflictProgress objects
156  // (one for each ModelSafeGroup currently in-use).
157  int TotalNumConflictingItems() const;
158
159  // Returns the number of updates received from the sync server.
160  int64 CountUpdates() const;
161
162  // Returns true iff any of the commit ids added during this session are
163  // bookmark related, and the bookmark group restriction is in effect.
164  bool HasBookmarkCommitActivity() const {
165    return ActiveGroupRestrictionIncludesModel(syncable::BOOKMARKS) &&
166        shared_.commit_set.HasBookmarkCommitId();
167  }
168
169  // Returns true if the last download_updates_command received a valid
170  // server response.
171  bool download_updates_succeeded() const {
172    return updates_response().has_get_updates();
173  }
174
175  // Returns true if the last updates response indicated that we were fully
176  // up to date.  This is subtle: if it's false, it could either mean that
177  // the server said there WAS more to download, or it could mean that we
178  // were unable to reach the server.  If we didn't request every enabled
179  // datatype, then we can't say for sure that there's nothing left to
180  // download: in that case, this also returns false.
181  bool ServerSaysNothingMoreToDownload() const;
182
183  ModelSafeGroup group_restriction() const {
184    return group_restriction_;
185  }
186
187  // Check whether a particular model is included by the active group
188  // restriction.
189  bool ActiveGroupRestrictionIncludesModel(syncable::ModelType model) const {
190    if (!group_restriction_in_effect_)
191      return true;
192    ModelSafeRoutingInfo::const_iterator it = routing_info_.find(model);
193    if (it == routing_info_.end())
194      return false;
195    return group_restriction() == it->second;
196  }
197
198  // A toolbelt full of methods for updating counters and flags.
199  void increment_num_conflicting_commits_by(int value);
200  void reset_num_conflicting_commits();
201  void set_num_consecutive_transient_error_commits(int value);
202  void increment_num_consecutive_transient_error_commits_by(int value);
203  void set_num_consecutive_errors(int value);
204  void increment_num_consecutive_errors();
205  void increment_num_consecutive_errors_by(int value);
206  void set_num_server_changes_remaining(int64 changes_remaining);
207  void set_invalid_store(bool invalid_store);
208  void set_syncer_stuck(bool syncer_stuck);
209  void set_syncing(bool syncing);
210  void set_num_successful_bookmark_commits(int value);
211  void increment_num_successful_commits();
212  void increment_num_successful_bookmark_commits();
213  void increment_num_updates_downloaded_by(int value);
214  void increment_num_tombstone_updates_downloaded_by(int value);
215  void set_types_needing_local_migration(const syncable::ModelTypeSet& types);
216  void set_unsynced_handles(const std::vector<int64>& unsynced_handles);
217
218  void set_commit_set(const OrderedCommitSet& commit_set);
219  void update_conflict_sets_built(bool built);
220  void update_conflicts_resolved(bool resolved);
221  void reset_conflicts_resolved();
222  void set_items_committed();
223
224 private:
225  friend class ScopedModelSafeGroupRestriction;
226
227  // Returns true iff the commit id projection for |group_restriction_|
228  // references position |index| into the full set of commit ids in play.
229  bool CurrentCommitIdProjectionHasIndex(size_t index);
230
231  // Helper to lazily create objects for per-ModelSafeGroup state.
232  PerModelSafeGroupState* GetOrCreateModelSafeGroupState(bool restrict,
233                                                         ModelSafeGroup group);
234
235  AllModelTypeState shared_;
236  std::map<ModelSafeGroup, PerModelSafeGroupState*> per_model_group_;
237
238  STLValueDeleter<std::map<ModelSafeGroup, PerModelSafeGroupState*> >
239      per_model_group_deleter_;
240
241  // Set to true if any DirtyOnWrite pieces of state we maintain are changed.
242  // Reset to false by TestAndClearIsDirty.
243  bool is_dirty_;
244
245  // Used to fail read/write operations on state that don't obey the current
246  // active ModelSafeWorker contract.
247  bool group_restriction_in_effect_;
248  ModelSafeGroup group_restriction_;
249
250  const ModelSafeRoutingInfo routing_info_;
251
252  DISALLOW_COPY_AND_ASSIGN(StatusController);
253};
254
255// A utility to restrict access to only those parts of the given
256// StatusController that pertain to the specified ModelSafeGroup.
257class ScopedModelSafeGroupRestriction {
258 public:
259  ScopedModelSafeGroupRestriction(StatusController* to_restrict,
260                                  ModelSafeGroup restriction)
261      : status_(to_restrict) {
262    DCHECK(!status_->group_restriction_in_effect_);
263    status_->group_restriction_ = restriction;
264    status_->group_restriction_in_effect_ = true;
265  }
266  ~ScopedModelSafeGroupRestriction() {
267    DCHECK(status_->group_restriction_in_effect_);
268    status_->group_restriction_in_effect_ = false;
269  }
270 private:
271  StatusController* status_;
272  DISALLOW_COPY_AND_ASSIGN(ScopedModelSafeGroupRestriction);
273};
274
275}
276}
277
278#endif  // CHROME_BROWSER_SYNC_SESSIONS_STATUS_CONTROLLER_H_
279