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.
9 #include "base/at_exit.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/debug/stack_trace.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/json/json_writer.h"
15 #include "base/logging.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/memory/weak_ptr.h"
19 #include "base/message_loop/message_loop.h"
20 #include "base/rand_util.h"
21 #include "base/task_runner.h"
22 #include "base/threading/thread.h"
23 #include "jingle/notifier/base/notification_method.h"
24 #include "jingle/notifier/base/notifier_options.h"
25 #include "net/base/host_port_pair.h"
26 #include "net/base/network_change_notifier.h"
27 #include "net/dns/host_resolver.h"
28 #include "net/http/transport_security_state.h"
29 #include "net/url_request/url_request_test_util.h"
30 #include "sync/internal_api/public/base/cancelation_signal.h"
31 #include "sync/internal_api/public/base/model_type.h"
32 #include "sync/internal_api/public/base_node.h"
33 #include "sync/internal_api/public/engine/passive_model_worker.h"
34 #include "sync/internal_api/public/http_bridge.h"
35 #include "sync/internal_api/public/internal_components_factory_impl.h"
36 #include "sync/internal_api/public/read_node.h"
37 #include "sync/internal_api/public/sync_manager.h"
38 #include "sync/internal_api/public/sync_manager_factory.h"
39 #include "sync/internal_api/public/util/report_unrecoverable_error_function.h"
40 #include "sync/internal_api/public/util/unrecoverable_error_handler.h"
41 #include "sync/internal_api/public/util/weak_handle.h"
42 #include "sync/js/js_event_details.h"
43 #include "sync/js/js_event_handler.h"
44 #include "sync/notifier/invalidation_state_tracker.h"
45 #include "sync/notifier/non_blocking_invalidator.h"
46 #include "sync/test/fake_encryptor.h"
47 #include "sync/tools/null_invalidation_state_tracker.h"
49 #if defined(OS_MACOSX)
50 #include "base/mac/scoped_nsautorelease_pool.h"
53 // This is a simple utility that initializes a sync client and
54 // prints out any events.
56 // TODO(akalin): Refactor to combine shared code with
57 // sync_listen_notifications.
61 const char kEmailSwitch
[] = "email";
62 const char kTokenSwitch
[] = "token";
63 const char kXmppHostPortSwitch
[] = "xmpp-host-port";
64 const char kXmppTrySslTcpFirstSwitch
[] = "xmpp-try-ssltcp-first";
65 const char kXmppAllowInsecureConnectionSwitch
[] =
66 "xmpp-allow-insecure-connection";
68 // Needed to use a real host resolver.
69 class MyTestURLRequestContext
: public net::TestURLRequestContext
{
71 MyTestURLRequestContext() : TestURLRequestContext(true) {
72 context_storage_
.set_host_resolver(
73 net::HostResolver::CreateDefaultResolver(NULL
));
74 context_storage_
.set_transport_security_state(
75 new net::TransportSecurityState());
79 virtual ~MyTestURLRequestContext() {}
82 class MyTestURLRequestContextGetter
: public net::TestURLRequestContextGetter
{
84 explicit MyTestURLRequestContextGetter(
85 const scoped_refptr
<base::MessageLoopProxy
>& io_message_loop_proxy
)
86 : TestURLRequestContextGetter(io_message_loop_proxy
) {}
88 virtual net::TestURLRequestContext
* GetURLRequestContext() OVERRIDE
{
89 // Construct |context_| lazily so it gets constructed on the right
90 // thread (the IO thread).
92 context_
.reset(new MyTestURLRequestContext());
93 return context_
.get();
97 virtual ~MyTestURLRequestContextGetter() {}
99 scoped_ptr
<MyTestURLRequestContext
> context_
;
102 // TODO(akalin): Use system encryptor once it's moved to sync/.
103 class NullEncryptor
: public Encryptor
{
105 virtual ~NullEncryptor() {}
107 virtual bool EncryptString(const std::string
& plaintext
,
108 std::string
* ciphertext
) OVERRIDE
{
109 *ciphertext
= plaintext
;
113 virtual bool DecryptString(const std::string
& ciphertext
,
114 std::string
* plaintext
) OVERRIDE
{
115 *plaintext
= ciphertext
;
120 std::string
ValueToString(const base::Value
& value
) {
122 base::JSONWriter::Write(&value
, &str
);
126 class LoggingChangeDelegate
: public SyncManager::ChangeDelegate
{
128 virtual ~LoggingChangeDelegate() {}
130 virtual void OnChangesApplied(
131 ModelType model_type
,
133 const BaseTransaction
* trans
,
134 const ImmutableChangeRecordList
& changes
) OVERRIDE
{
135 LOG(INFO
) << "Changes applied for "
136 << ModelTypeToString(model_type
);
138 size_t change_count
= changes
.Get().size();
139 for (ChangeRecordList::const_iterator it
=
140 changes
.Get().begin(); it
!= changes
.Get().end(); ++it
) {
141 scoped_ptr
<base::DictionaryValue
> change_value(it
->ToValue());
142 LOG(INFO
) << "Change (" << i
<< "/" << change_count
<< "): "
143 << ValueToString(*change_value
);
144 if (it
->action
!= ChangeRecord::ACTION_DELETE
) {
145 ReadNode
node(trans
);
146 CHECK_EQ(node
.InitByIdLookup(it
->id
), BaseNode::INIT_OK
);
147 scoped_ptr
<base::DictionaryValue
> details(node
.ToValue());
148 VLOG(1) << "Details: " << ValueToString(*details
);
154 virtual void OnChangesComplete(ModelType model_type
) OVERRIDE
{
155 LOG(INFO
) << "Changes complete for "
156 << ModelTypeToString(model_type
);
160 class LoggingUnrecoverableErrorHandler
161 : public UnrecoverableErrorHandler
{
163 virtual ~LoggingUnrecoverableErrorHandler() {}
165 virtual void OnUnrecoverableError(const tracked_objects::Location
& from_here
,
166 const std::string
& message
) OVERRIDE
{
167 if (LOG_IS_ON(ERROR
)) {
168 logging::LogMessage(from_here
.file_name(), from_here
.line_number(),
169 logging::LOG_ERROR
).stream()
175 class LoggingJsEventHandler
176 : public JsEventHandler
,
177 public base::SupportsWeakPtr
<LoggingJsEventHandler
> {
179 virtual ~LoggingJsEventHandler() {}
181 virtual void HandleJsEvent(
182 const std::string
& name
,
183 const JsEventDetails
& details
) OVERRIDE
{
184 VLOG(1) << name
<< ": " << details
.ToString();
188 void LogUnrecoverableErrorContext() {
189 base::debug::StackTrace().Print();
192 notifier::NotifierOptions
ParseNotifierOptions(
193 const CommandLine
& command_line
,
194 const scoped_refptr
<net::URLRequestContextGetter
>&
195 request_context_getter
) {
196 notifier::NotifierOptions notifier_options
;
197 notifier_options
.request_context_getter
= request_context_getter
;
198 notifier_options
.auth_mechanism
= "X-OAUTH2";
200 if (command_line
.HasSwitch(kXmppHostPortSwitch
)) {
201 notifier_options
.xmpp_host_port
=
202 net::HostPortPair::FromString(
203 command_line
.GetSwitchValueASCII(kXmppHostPortSwitch
));
204 LOG(INFO
) << "Using " << notifier_options
.xmpp_host_port
.ToString()
205 << " for test sync notification server.";
208 notifier_options
.try_ssltcp_first
=
209 command_line
.HasSwitch(kXmppTrySslTcpFirstSwitch
);
210 LOG_IF(INFO
, notifier_options
.try_ssltcp_first
)
211 << "Trying SSL/TCP port before XMPP port for notifications.";
213 notifier_options
.allow_insecure_connection
=
214 command_line
.HasSwitch(kXmppAllowInsecureConnectionSwitch
);
215 LOG_IF(INFO
, notifier_options
.allow_insecure_connection
)
216 << "Allowing insecure XMPP connections.";
218 return notifier_options
;
221 void StubNetworkTimeUpdateCallback(const base::Time
&,
222 const base::TimeDelta
&,
223 const base::TimeDelta
&) {
226 int SyncClientMain(int argc
, char* argv
[]) {
227 #if defined(OS_MACOSX)
228 base::mac::ScopedNSAutoreleasePool pool
;
230 base::AtExitManager exit_manager
;
231 CommandLine::Init(argc
, argv
);
232 logging::LoggingSettings settings
;
233 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
234 logging::InitLogging(settings
);
236 base::MessageLoop sync_loop
;
237 base::Thread
io_thread("IO thread");
238 base::Thread::Options options
;
239 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
240 io_thread
.StartWithOptions(options
);
242 // Parse command line.
243 const CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
244 SyncCredentials credentials
;
245 credentials
.email
= command_line
.GetSwitchValueASCII(kEmailSwitch
);
246 credentials
.sync_token
= command_line
.GetSwitchValueASCII(kTokenSwitch
);
247 // TODO(akalin): Write a wrapper script that gets a token for an
248 // email and password and passes that in to this utility.
249 if (credentials
.email
.empty() || credentials
.sync_token
.empty()) {
250 std::printf("Usage: %s --%s=foo@bar.com --%s=token\n"
251 "[--%s=host:port] [--%s] [--%s]\n"
252 "Run chrome and set a breakpoint on\n"
253 "syncer::SyncManagerImpl::UpdateCredentials() "
254 "after logging into\n"
255 "sync to get the token to pass into this utility.\n",
257 kEmailSwitch
, kTokenSwitch
, kXmppHostPortSwitch
,
258 kXmppTrySslTcpFirstSwitch
,
259 kXmppAllowInsecureConnectionSwitch
);
263 // Set up objects that monitor the network.
264 scoped_ptr
<net::NetworkChangeNotifier
> network_change_notifier(
265 net::NetworkChangeNotifier::Create());
267 // Set up sync notifier factory.
268 const scoped_refptr
<MyTestURLRequestContextGetter
> context_getter
=
269 new MyTestURLRequestContextGetter(io_thread
.message_loop_proxy());
270 const notifier::NotifierOptions
& notifier_options
=
271 ParseNotifierOptions(command_line
, context_getter
);
272 syncer::NetworkChannelCreator network_channel_creator
=
273 syncer::NonBlockingInvalidator::MakePushClientChannelCreator(
275 const char kClientInfo
[] = "standalone_sync_client";
276 std::string invalidator_id
= base::RandBytesAsString(8);
277 NullInvalidationStateTracker null_invalidation_state_tracker
;
278 scoped_ptr
<Invalidator
> invalidator(new NonBlockingInvalidator(
279 network_channel_creator
,
281 null_invalidation_state_tracker
.GetSavedInvalidations(),
282 null_invalidation_state_tracker
.GetBootstrapData(),
283 WeakHandle
<InvalidationStateTracker
>(
284 null_invalidation_state_tracker
.AsWeakPtr()),
286 notifier_options
.request_context_getter
));
288 // Set up database directory for the syncer.
289 base::ScopedTempDir database_dir
;
290 CHECK(database_dir
.CreateUniqueTempDir());
292 // Developers often add types to ModelTypeSet::All() before the server
293 // supports them. We need to be explicit about which types we want here.
294 ModelTypeSet model_types
;
295 model_types
.Put(BOOKMARKS
);
296 model_types
.Put(PREFERENCES
);
297 model_types
.Put(PASSWORDS
);
298 model_types
.Put(AUTOFILL
);
299 model_types
.Put(THEMES
);
300 model_types
.Put(TYPED_URLS
);
301 model_types
.Put(EXTENSIONS
);
302 model_types
.Put(NIGORI
);
303 model_types
.Put(SEARCH_ENGINES
);
304 model_types
.Put(SESSIONS
);
305 model_types
.Put(APPS
);
306 model_types
.Put(AUTOFILL_PROFILE
);
307 model_types
.Put(APP_SETTINGS
);
308 model_types
.Put(EXTENSION_SETTINGS
);
309 model_types
.Put(APP_NOTIFICATIONS
);
310 model_types
.Put(HISTORY_DELETE_DIRECTIVES
);
311 model_types
.Put(SYNCED_NOTIFICATIONS
);
312 model_types
.Put(DEVICE_INFO
);
313 model_types
.Put(EXPERIMENTS
);
314 model_types
.Put(PRIORITY_PREFERENCES
);
315 model_types
.Put(DICTIONARY
);
316 model_types
.Put(FAVICON_IMAGES
);
317 model_types
.Put(FAVICON_TRACKING
);
319 ModelSafeRoutingInfo routing_info
;
320 for (ModelTypeSet::Iterator it
= model_types
.First();
321 it
.Good(); it
.Inc()) {
322 routing_info
[it
.Get()] = GROUP_PASSIVE
;
324 scoped_refptr
<PassiveModelWorker
> passive_model_safe_worker
=
325 new PassiveModelWorker(&sync_loop
, NULL
);
326 std::vector
<scoped_refptr
<ModelSafeWorker
> > workers
;
327 workers
.push_back(passive_model_safe_worker
);
329 // Set up sync manager.
330 SyncManagerFactory sync_manager_factory
;
331 scoped_ptr
<SyncManager
> sync_manager
=
332 sync_manager_factory
.CreateSyncManager("sync_client manager");
333 LoggingJsEventHandler js_event_handler
;
334 const char kSyncServerAndPath
[] = "clients4.google.com/chrome-sync/dev";
335 int kSyncServerPort
= 443;
337 // Used only by InitialProcessMetadata(), so it's okay to leave this as NULL.
338 const scoped_refptr
<base::TaskRunner
> blocking_task_runner
= NULL
;
339 const char kUserAgent
[] = "sync_client";
340 // TODO(akalin): Replace this with just the context getter once
341 // HttpPostProviderFactory is removed.
342 CancelationSignal factory_cancelation_signal
;
343 scoped_ptr
<HttpPostProviderFactory
> post_factory(
344 new HttpBridgeFactory(context_getter
.get(),
345 base::Bind(&StubNetworkTimeUpdateCallback
),
346 &factory_cancelation_signal
));
347 post_factory
->Init(kUserAgent
);
348 // Used only when committing bookmarks, so it's okay to leave this
350 ExtensionsActivity
* extensions_activity
= NULL
;
351 LoggingChangeDelegate change_delegate
;
352 const char kRestoredKeyForBootstrapping
[] = "";
353 const char kRestoredKeystoreKeyForBootstrapping
[] = "";
354 NullEncryptor null_encryptor
;
355 InternalComponentsFactoryImpl::Switches factory_switches
= {
356 InternalComponentsFactory::ENCRYPTION_KEYSTORE
,
357 InternalComponentsFactory::BACKOFF_NORMAL
359 CancelationSignal scm_cancelation_signal
;
361 sync_manager
->Init(database_dir
.path(),
362 WeakHandle
<JsEventHandler
>(
363 js_event_handler
.AsWeakPtr()),
373 kRestoredKeyForBootstrapping
,
374 kRestoredKeystoreKeyForBootstrapping
,
375 new InternalComponentsFactoryImpl(factory_switches
),
377 scoped_ptr
<UnrecoverableErrorHandler
>(
378 new LoggingUnrecoverableErrorHandler
).Pass(),
379 &LogUnrecoverableErrorContext
,
380 &scm_cancelation_signal
);
381 // TODO(akalin): Avoid passing in model parameters multiple times by
382 // organizing handling of model types.
383 invalidator
->UpdateCredentials(credentials
.email
, credentials
.sync_token
);
384 invalidator
->RegisterHandler(sync_manager
.get());
385 invalidator
->UpdateRegisteredIds(
386 sync_manager
.get(), ModelTypeSetToObjectIdSet(model_types
));
387 sync_manager
->StartSyncingNormally(routing_info
);
396 } // namespace syncer
398 int main(int argc
, char* argv
[]) {
399 return syncer::SyncClientMain(argc
, argv
);