1// Copyright (c) 2012 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 "sync/engine/syncer.h"
6
7#include "base/debug/trace_event.h"
8#include "base/location.h"
9#include "base/logging.h"
10#include "base/message_loop/message_loop.h"
11#include "base/time/time.h"
12#include "build/build_config.h"
13#include "sync/engine/apply_control_data_updates.h"
14#include "sync/engine/commit.h"
15#include "sync/engine/commit_processor.h"
16#include "sync/engine/conflict_resolver.h"
17#include "sync/engine/get_updates_delegate.h"
18#include "sync/engine/get_updates_processor.h"
19#include "sync/engine/net/server_connection_manager.h"
20#include "sync/engine/syncer_types.h"
21#include "sync/internal_api/public/base/cancelation_signal.h"
22#include "sync/internal_api/public/base/unique_position.h"
23#include "sync/internal_api/public/util/syncer_error.h"
24#include "sync/sessions/nudge_tracker.h"
25#include "sync/syncable/directory.h"
26#include "sync/syncable/mutable_entry.h"
27#include "sync/syncable/syncable-inl.h"
28
29using base::Time;
30using base::TimeDelta;
31using sync_pb::ClientCommand;
32
33namespace syncer {
34
35// TODO(akalin): We may want to propagate this switch up
36// eventually.
37#if defined(OS_ANDROID) || defined(OS_IOS)
38static const bool kCreateMobileBookmarksFolder = true;
39#else
40static const bool kCreateMobileBookmarksFolder = false;
41#endif
42
43using sessions::StatusController;
44using sessions::SyncSession;
45using sessions::NudgeTracker;
46
47Syncer::Syncer(syncer::CancelationSignal* cancelation_signal)
48    : cancelation_signal_(cancelation_signal) {
49}
50
51Syncer::~Syncer() {}
52
53bool Syncer::ExitRequested() {
54  return cancelation_signal_->IsSignalled();
55}
56
57bool Syncer::NormalSyncShare(ModelTypeSet request_types,
58                             const NudgeTracker& nudge_tracker,
59                             SyncSession* session) {
60  HandleCycleBegin(session);
61  if (nudge_tracker.IsGetUpdatesRequired() ||
62      session->context()->ShouldFetchUpdatesBeforeCommit()) {
63    VLOG(1) << "Downloading types " << ModelTypeSetToString(request_types);
64    NormalGetUpdatesDelegate normal_delegate(nudge_tracker);
65    GetUpdatesProcessor get_updates_processor(
66        session->context()->model_type_registry()->update_handler_map(),
67        normal_delegate);
68    if (!DownloadAndApplyUpdates(
69            request_types,
70            session,
71            &get_updates_processor,
72            kCreateMobileBookmarksFolder)) {
73      return HandleCycleEnd(session, nudge_tracker.GetLegacySource());
74    }
75  }
76
77  VLOG(1) << "Committing from types " << ModelTypeSetToString(request_types);
78  CommitProcessor commit_processor(
79      session->context()->model_type_registry()->commit_contributor_map());
80  SyncerError commit_result =
81      BuildAndPostCommits(request_types, session, &commit_processor);
82  session->mutable_status_controller()->set_commit_result(commit_result);
83
84  return HandleCycleEnd(session, nudge_tracker.GetLegacySource());
85}
86
87bool Syncer::ConfigureSyncShare(
88    ModelTypeSet request_types,
89    sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source,
90    SyncSession* session) {
91  VLOG(1) << "Configuring types " << ModelTypeSetToString(request_types);
92  HandleCycleBegin(session);
93  ConfigureGetUpdatesDelegate configure_delegate(source);
94  GetUpdatesProcessor get_updates_processor(
95      session->context()->model_type_registry()->update_handler_map(),
96      configure_delegate);
97  DownloadAndApplyUpdates(
98      request_types,
99      session,
100      &get_updates_processor,
101      kCreateMobileBookmarksFolder);
102  return HandleCycleEnd(session, source);
103}
104
105bool Syncer::PollSyncShare(ModelTypeSet request_types,
106                           SyncSession* session) {
107  VLOG(1) << "Polling types " << ModelTypeSetToString(request_types);
108  HandleCycleBegin(session);
109  PollGetUpdatesDelegate poll_delegate;
110  GetUpdatesProcessor get_updates_processor(
111      session->context()->model_type_registry()->update_handler_map(),
112      poll_delegate);
113  DownloadAndApplyUpdates(
114      request_types,
115      session,
116      &get_updates_processor,
117      kCreateMobileBookmarksFolder);
118  return HandleCycleEnd(session, sync_pb::GetUpdatesCallerInfo::PERIODIC);
119}
120
121bool Syncer::DownloadAndApplyUpdates(
122    ModelTypeSet request_types,
123    SyncSession* session,
124    GetUpdatesProcessor* get_updates_processor,
125    bool create_mobile_bookmarks_folder) {
126  SyncerError download_result = UNSET;
127  do {
128    download_result = get_updates_processor->DownloadUpdates(
129        request_types,
130        session,
131        create_mobile_bookmarks_folder);
132  } while (download_result == SERVER_MORE_TO_DOWNLOAD);
133
134  // Exit without applying if we're shutting down or an error was detected.
135  if (download_result != SYNCER_OK)
136    return false;
137  if (ExitRequested())
138    return false;
139
140  {
141    TRACE_EVENT0("sync", "ApplyUpdates");
142
143    // Control type updates always get applied first.
144    ApplyControlDataUpdates(session->context()->directory());
145
146    // Apply upates to the other types.  May or may not involve cross-thread
147    // traffic, depending on the underlying update handlers and the GU type's
148    // delegate.
149    get_updates_processor->ApplyUpdates(request_types,
150                                        session->mutable_status_controller());
151
152    session->context()->set_hierarchy_conflict_detected(
153        session->status_controller().num_hierarchy_conflicts() > 0);
154    session->SendEventNotification(SyncCycleEvent::STATUS_CHANGED);
155  }
156
157  if (ExitRequested())
158    return false;
159  return true;
160}
161
162SyncerError Syncer::BuildAndPostCommits(ModelTypeSet requested_types,
163                                        sessions::SyncSession* session,
164                                        CommitProcessor* commit_processor) {
165  // The ExitRequested() check is unnecessary, since we should start getting
166  // errors from the ServerConnectionManager if an exist has been requested.
167  // However, it doesn't hurt to check it anyway.
168  while (!ExitRequested()) {
169    scoped_ptr<Commit> commit(
170        Commit::Init(
171            requested_types,
172            session->context()->GetEnabledTypes(),
173            session->context()->max_commit_batch_size(),
174            session->context()->account_name(),
175            session->context()->directory()->cache_guid(),
176            commit_processor,
177            session->context()->extensions_activity()));
178    if (!commit) {
179      break;
180    }
181
182    SyncerError error = commit->PostAndProcessResponse(
183        session,
184        session->mutable_status_controller(),
185        session->context()->extensions_activity());
186    commit->CleanUp();
187    if (error != SYNCER_OK) {
188      return error;
189    }
190  }
191
192  return SYNCER_OK;
193}
194
195void Syncer::HandleCycleBegin(SyncSession* session) {
196  session->mutable_status_controller()->UpdateStartTime();
197  session->SendEventNotification(SyncCycleEvent::SYNC_CYCLE_BEGIN);
198}
199
200bool Syncer::HandleCycleEnd(
201    SyncSession* session,
202    sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source) {
203  if (!ExitRequested()) {
204    session->SendSyncCycleEndEventNotification(source);
205    return true;
206  } else {
207    return false;
208  }
209}
210
211}  // namespace syncer
212