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 "components/invalidation/non_blocking_invalidator.h"
24 #include "components/invalidation/object_id_invalidation_map.h"
25 #include "jingle/notifier/base/notification_method.h"
26 #include "jingle/notifier/base/notifier_options.h"
27 #include "net/base/host_port_pair.h"
28 #include "net/base/network_change_notifier.h"
29 #include "net/dns/host_resolver.h"
30 #include "net/http/transport_security_state.h"
31 #include "net/url_request/url_request_test_util.h"
32 #include "sync/internal_api/public/base/cancelation_signal.h"
33 #include "sync/internal_api/public/base/model_type.h"
34 #include "sync/internal_api/public/base_node.h"
35 #include "sync/internal_api/public/engine/passive_model_worker.h"
36 #include "sync/internal_api/public/http_bridge.h"
37 #include "sync/internal_api/public/internal_components_factory_impl.h"
38 #include "sync/internal_api/public/read_node.h"
39 #include "sync/internal_api/public/sync_manager.h"
40 #include "sync/internal_api/public/sync_manager_factory.h"
41 #include "sync/internal_api/public/util/report_unrecoverable_error_function.h"
42 #include "sync/internal_api/public/util/unrecoverable_error_handler.h"
43 #include "sync/internal_api/public/util/weak_handle.h"
44 #include "sync/js/js_event_details.h"
45 #include "sync/js/js_event_handler.h"
46 #include "sync/test/fake_encryptor.h"
47 #include "sync/tools/invalidation_helper.h"
48 #include "sync/tools/null_invalidation_state_tracker.h"
51 #if defined(OS_MACOSX)
52 #include "base/mac/scoped_nsautorelease_pool.h"
55 // This is a simple utility that initializes a sync client and
56 // prints out any events.
58 // TODO(akalin): Refactor to combine shared code with
59 // sync_listen_notifications.
63 const char kEmailSwitch
[] = "email";
64 const char kTokenSwitch
[] = "token";
65 const char kXmppHostPortSwitch
[] = "xmpp-host-port";
66 const char kXmppTrySslTcpFirstSwitch
[] = "xmpp-try-ssltcp-first";
67 const char kXmppAllowInsecureConnectionSwitch
[] =
68 "xmpp-allow-insecure-connection";
69 const char kSyncServiceURL
[] = "https://clients4.google.com/chrome-sync/dev";
71 // Needed to use a real host resolver.
72 class MyTestURLRequestContext
: public net::TestURLRequestContext
{
74 MyTestURLRequestContext() : TestURLRequestContext(true) {
75 context_storage_
.set_host_resolver(
76 net::HostResolver::CreateDefaultResolver(NULL
));
77 context_storage_
.set_transport_security_state(
78 new net::TransportSecurityState());
82 virtual ~MyTestURLRequestContext() {}
85 class MyTestURLRequestContextGetter
: public net::TestURLRequestContextGetter
{
87 explicit MyTestURLRequestContextGetter(
88 const scoped_refptr
<base::SingleThreadTaskRunner
>& io_task_runner
)
89 : TestURLRequestContextGetter(io_task_runner
) {}
91 virtual net::TestURLRequestContext
* GetURLRequestContext() OVERRIDE
{
92 // Construct |context_| lazily so it gets constructed on the right
93 // thread (the IO thread).
95 context_
.reset(new MyTestURLRequestContext());
96 return context_
.get();
100 virtual ~MyTestURLRequestContextGetter() {}
102 scoped_ptr
<MyTestURLRequestContext
> context_
;
105 // TODO(akalin): Use system encryptor once it's moved to sync/.
106 class NullEncryptor
: public Encryptor
{
108 virtual ~NullEncryptor() {}
110 virtual bool EncryptString(const std::string
& plaintext
,
111 std::string
* ciphertext
) OVERRIDE
{
112 *ciphertext
= plaintext
;
116 virtual bool DecryptString(const std::string
& ciphertext
,
117 std::string
* plaintext
) OVERRIDE
{
118 *plaintext
= ciphertext
;
123 std::string
ValueToString(const base::Value
& value
) {
125 base::JSONWriter::Write(&value
, &str
);
129 class LoggingChangeDelegate
: public SyncManager::ChangeDelegate
{
131 virtual ~LoggingChangeDelegate() {}
133 virtual void OnChangesApplied(
134 ModelType model_type
,
136 const BaseTransaction
* trans
,
137 const ImmutableChangeRecordList
& changes
) OVERRIDE
{
138 LOG(INFO
) << "Changes applied for "
139 << ModelTypeToString(model_type
);
141 size_t change_count
= changes
.Get().size();
142 for (ChangeRecordList::const_iterator it
=
143 changes
.Get().begin(); it
!= changes
.Get().end(); ++it
) {
144 scoped_ptr
<base::DictionaryValue
> change_value(it
->ToValue());
145 LOG(INFO
) << "Change (" << i
<< "/" << change_count
<< "): "
146 << ValueToString(*change_value
);
147 if (it
->action
!= ChangeRecord::ACTION_DELETE
) {
148 ReadNode
node(trans
);
149 CHECK_EQ(node
.InitByIdLookup(it
->id
), BaseNode::INIT_OK
);
150 scoped_ptr
<base::DictionaryValue
> details(node
.ToValue());
151 VLOG(1) << "Details: " << ValueToString(*details
);
157 virtual void OnChangesComplete(ModelType model_type
) OVERRIDE
{
158 LOG(INFO
) << "Changes complete for "
159 << ModelTypeToString(model_type
);
163 class LoggingUnrecoverableErrorHandler
164 : public UnrecoverableErrorHandler
{
166 virtual ~LoggingUnrecoverableErrorHandler() {}
168 virtual void OnUnrecoverableError(const tracked_objects::Location
& from_here
,
169 const std::string
& message
) OVERRIDE
{
170 if (LOG_IS_ON(ERROR
)) {
171 logging::LogMessage(from_here
.file_name(), from_here
.line_number(),
172 logging::LOG_ERROR
).stream()
178 class LoggingJsEventHandler
179 : public JsEventHandler
,
180 public base::SupportsWeakPtr
<LoggingJsEventHandler
> {
182 virtual ~LoggingJsEventHandler() {}
184 virtual void HandleJsEvent(
185 const std::string
& name
,
186 const JsEventDetails
& details
) OVERRIDE
{
187 VLOG(1) << name
<< ": " << details
.ToString();
191 class InvalidationAdapter
: public syncer::InvalidationInterface
{
193 explicit InvalidationAdapter(const syncer::Invalidation
& invalidation
)
194 : invalidation_(invalidation
) {}
195 virtual ~InvalidationAdapter() {}
197 virtual bool IsUnknownVersion() const OVERRIDE
{
198 return invalidation_
.is_unknown_version();
201 virtual const std::string
& GetPayload() const OVERRIDE
{
202 return invalidation_
.payload();
205 virtual int64
GetVersion() const OVERRIDE
{
206 return invalidation_
.version();
209 virtual void Acknowledge() OVERRIDE
{
210 invalidation_
.Acknowledge();
213 virtual void Drop() OVERRIDE
{
214 invalidation_
.Drop();
218 syncer::Invalidation invalidation_
;
221 class InvalidatorShim
: public InvalidationHandler
{
223 explicit InvalidatorShim(SyncManager
* sync_manager
)
224 : sync_manager_(sync_manager
) {}
226 virtual void OnInvalidatorStateChange(InvalidatorState state
) OVERRIDE
{
227 sync_manager_
->SetInvalidatorEnabled(state
== INVALIDATIONS_ENABLED
);
230 virtual void OnIncomingInvalidation(
231 const ObjectIdInvalidationMap
& invalidation_map
) OVERRIDE
{
232 syncer::ObjectIdSet ids
= invalidation_map
.GetObjectIds();
233 for (syncer::ObjectIdSet::const_iterator ids_it
= ids
.begin();
236 syncer::ModelType type
;
237 if (!NotificationTypeToRealModelType(ids_it
->name(), &type
)) {
238 DLOG(WARNING
) << "Notification has invalid id: "
239 << syncer::ObjectIdToString(*ids_it
);
241 syncer::SingleObjectInvalidationSet invalidation_set
=
242 invalidation_map
.ForObject(*ids_it
);
243 for (syncer::SingleObjectInvalidationSet::const_iterator inv_it
=
244 invalidation_set
.begin();
245 inv_it
!= invalidation_set
.end();
247 scoped_ptr
<syncer::InvalidationInterface
> inv_adapter(
248 new InvalidationAdapter(*inv_it
));
249 sync_manager_
->OnIncomingInvalidation(type
, inv_adapter
.Pass());
255 virtual std::string
GetOwnerName() const OVERRIDE
{
256 return "InvalidatorShim";
260 SyncManager
* sync_manager_
;
263 void LogUnrecoverableErrorContext() {
264 base::debug::StackTrace().Print();
267 notifier::NotifierOptions
ParseNotifierOptions(
268 const CommandLine
& command_line
,
269 const scoped_refptr
<net::URLRequestContextGetter
>&
270 request_context_getter
) {
271 notifier::NotifierOptions notifier_options
;
272 notifier_options
.request_context_getter
= request_context_getter
;
273 notifier_options
.auth_mechanism
= "X-OAUTH2";
275 if (command_line
.HasSwitch(kXmppHostPortSwitch
)) {
276 notifier_options
.xmpp_host_port
=
277 net::HostPortPair::FromString(
278 command_line
.GetSwitchValueASCII(kXmppHostPortSwitch
));
279 LOG(INFO
) << "Using " << notifier_options
.xmpp_host_port
.ToString()
280 << " for test sync notification server.";
283 notifier_options
.try_ssltcp_first
=
284 command_line
.HasSwitch(kXmppTrySslTcpFirstSwitch
);
285 LOG_IF(INFO
, notifier_options
.try_ssltcp_first
)
286 << "Trying SSL/TCP port before XMPP port for notifications.";
288 notifier_options
.allow_insecure_connection
=
289 command_line
.HasSwitch(kXmppAllowInsecureConnectionSwitch
);
290 LOG_IF(INFO
, notifier_options
.allow_insecure_connection
)
291 << "Allowing insecure XMPP connections.";
293 return notifier_options
;
296 void StubNetworkTimeUpdateCallback(const base::Time
&,
297 const base::TimeDelta
&,
298 const base::TimeDelta
&) {
301 int SyncClientMain(int argc
, char* argv
[]) {
302 #if defined(OS_MACOSX)
303 base::mac::ScopedNSAutoreleasePool pool
;
305 base::AtExitManager exit_manager
;
306 CommandLine::Init(argc
, argv
);
307 logging::LoggingSettings settings
;
308 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
309 logging::InitLogging(settings
);
311 base::MessageLoop sync_loop
;
312 base::Thread
io_thread("IO thread");
313 base::Thread::Options options
;
314 options
.message_loop_type
= base::MessageLoop::TYPE_IO
;
315 io_thread
.StartWithOptions(options
);
317 // Parse command line.
318 const CommandLine
& command_line
= *CommandLine::ForCurrentProcess();
319 SyncCredentials credentials
;
320 credentials
.email
= command_line
.GetSwitchValueASCII(kEmailSwitch
);
321 credentials
.sync_token
= command_line
.GetSwitchValueASCII(kTokenSwitch
);
322 // TODO(akalin): Write a wrapper script that gets a token for an
323 // email and password and passes that in to this utility.
324 if (credentials
.email
.empty() || credentials
.sync_token
.empty()) {
325 std::printf("Usage: %s --%s=foo@bar.com --%s=token\n"
326 "[--%s=host:port] [--%s] [--%s]\n"
327 "Run chrome and set a breakpoint on\n"
328 "syncer::SyncManagerImpl::UpdateCredentials() "
329 "after logging into\n"
330 "sync to get the token to pass into this utility.\n",
332 kEmailSwitch
, kTokenSwitch
, kXmppHostPortSwitch
,
333 kXmppTrySslTcpFirstSwitch
,
334 kXmppAllowInsecureConnectionSwitch
);
338 // Set up objects that monitor the network.
339 scoped_ptr
<net::NetworkChangeNotifier
> network_change_notifier(
340 net::NetworkChangeNotifier::Create());
342 // Set up sync notifier factory.
343 const scoped_refptr
<MyTestURLRequestContextGetter
> context_getter
=
344 new MyTestURLRequestContextGetter(io_thread
.message_loop_proxy());
345 const notifier::NotifierOptions
& notifier_options
=
346 ParseNotifierOptions(command_line
, context_getter
);
347 syncer::NetworkChannelCreator network_channel_creator
=
348 syncer::NonBlockingInvalidator::MakePushClientChannelCreator(
350 const char kClientInfo
[] = "standalone_sync_client";
351 std::string invalidator_id
= base::RandBytesAsString(8);
352 NullInvalidationStateTracker null_invalidation_state_tracker
;
353 scoped_ptr
<Invalidator
> invalidator(new NonBlockingInvalidator(
354 network_channel_creator
,
356 null_invalidation_state_tracker
.GetSavedInvalidations(),
357 null_invalidation_state_tracker
.GetBootstrapData(),
358 &null_invalidation_state_tracker
,
360 notifier_options
.request_context_getter
));
362 // Set up database directory for the syncer.
363 base::ScopedTempDir database_dir
;
364 CHECK(database_dir
.CreateUniqueTempDir());
366 // Developers often add types to ModelTypeSet::All() before the server
367 // supports them. We need to be explicit about which types we want here.
368 ModelTypeSet model_types
;
369 model_types
.Put(BOOKMARKS
);
370 model_types
.Put(PREFERENCES
);
371 model_types
.Put(PASSWORDS
);
372 model_types
.Put(AUTOFILL
);
373 model_types
.Put(THEMES
);
374 model_types
.Put(TYPED_URLS
);
375 model_types
.Put(EXTENSIONS
);
376 model_types
.Put(NIGORI
);
377 model_types
.Put(SEARCH_ENGINES
);
378 model_types
.Put(SESSIONS
);
379 model_types
.Put(APPS
);
380 model_types
.Put(AUTOFILL_PROFILE
);
381 model_types
.Put(APP_SETTINGS
);
382 model_types
.Put(EXTENSION_SETTINGS
);
383 model_types
.Put(APP_NOTIFICATIONS
);
384 model_types
.Put(HISTORY_DELETE_DIRECTIVES
);
385 model_types
.Put(SYNCED_NOTIFICATIONS
);
386 model_types
.Put(SYNCED_NOTIFICATION_APP_INFO
);
387 model_types
.Put(DEVICE_INFO
);
388 model_types
.Put(EXPERIMENTS
);
389 model_types
.Put(PRIORITY_PREFERENCES
);
390 model_types
.Put(DICTIONARY
);
391 model_types
.Put(FAVICON_IMAGES
);
392 model_types
.Put(FAVICON_TRACKING
);
394 ModelSafeRoutingInfo routing_info
;
395 for (ModelTypeSet::Iterator it
= model_types
.First();
396 it
.Good(); it
.Inc()) {
397 routing_info
[it
.Get()] = GROUP_PASSIVE
;
399 scoped_refptr
<PassiveModelWorker
> passive_model_safe_worker
=
400 new PassiveModelWorker(&sync_loop
, NULL
);
401 std::vector
<scoped_refptr
<ModelSafeWorker
> > workers
;
402 workers
.push_back(passive_model_safe_worker
);
404 // Set up sync manager.
405 SyncManagerFactory
sync_manager_factory(SyncManagerFactory::NORMAL
);
406 scoped_ptr
<SyncManager
> sync_manager
=
407 sync_manager_factory
.CreateSyncManager("sync_client manager");
408 LoggingJsEventHandler js_event_handler
;
409 // Used only by InitialProcessMetadata(), so it's okay to leave this as NULL.
410 const scoped_refptr
<base::TaskRunner
> blocking_task_runner
= NULL
;
411 const char kUserAgent
[] = "sync_client";
412 // TODO(akalin): Replace this with just the context getter once
413 // HttpPostProviderFactory is removed.
414 CancelationSignal factory_cancelation_signal
;
415 scoped_ptr
<HttpPostProviderFactory
> post_factory(
416 new HttpBridgeFactory(context_getter
.get(),
417 base::Bind(&StubNetworkTimeUpdateCallback
),
418 &factory_cancelation_signal
));
419 post_factory
->Init(kUserAgent
);
420 // Used only when committing bookmarks, so it's okay to leave this
422 ExtensionsActivity
* extensions_activity
= NULL
;
423 LoggingChangeDelegate change_delegate
;
424 const char kRestoredKeyForBootstrapping
[] = "";
425 const char kRestoredKeystoreKeyForBootstrapping
[] = "";
426 NullEncryptor null_encryptor
;
427 InternalComponentsFactoryImpl::Switches factory_switches
= {
428 InternalComponentsFactory::ENCRYPTION_KEYSTORE
,
429 InternalComponentsFactory::BACKOFF_NORMAL
431 CancelationSignal scm_cancelation_signal
;
433 SyncManager::InitArgs args
;
434 args
.database_location
= database_dir
.path();
435 args
.event_handler
= WeakHandle
<JsEventHandler
>(js_event_handler
.AsWeakPtr());
436 args
.service_url
= GURL(kSyncServiceURL
);
437 args
.post_factory
= post_factory
.Pass();
438 args
.workers
= workers
;
439 args
.extensions_activity
= extensions_activity
;
440 args
.change_delegate
= &change_delegate
;
441 args
.credentials
= credentials
;
442 args
.invalidator_client_id
= invalidator_id
;
443 args
.restored_key_for_bootstrapping
= kRestoredKeyForBootstrapping
;
444 args
.restored_keystore_key_for_bootstrapping
=
445 kRestoredKeystoreKeyForBootstrapping
;
446 args
.internal_components_factory
.reset(
447 new InternalComponentsFactoryImpl(factory_switches
));
448 args
.encryptor
= &null_encryptor
;
449 args
.unrecoverable_error_handler
.reset(new LoggingUnrecoverableErrorHandler
);
450 args
.report_unrecoverable_error_function
= &LogUnrecoverableErrorContext
;
451 args
.cancelation_signal
= &scm_cancelation_signal
;
452 sync_manager
->Init(&args
);
453 // TODO(akalin): Avoid passing in model parameters multiple times by
454 // organizing handling of model types.
455 invalidator
->UpdateCredentials(credentials
.email
, credentials
.sync_token
);
456 scoped_ptr
<InvalidatorShim
> shim(new InvalidatorShim(sync_manager
.get()));
457 invalidator
->RegisterHandler(shim
.get());
458 invalidator
->UpdateRegisteredIds(
459 shim
.get(), ModelTypeSetToObjectIdSet(model_types
));
460 sync_manager
->StartSyncingNormally(routing_info
);
469 } // namespace syncer
471 int main(int argc
, char* argv
[]) {
472 return syncer::SyncClientMain(argc
, argv
);