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.
5 #include "sync/internal_api/public/base/model_type.h"
7 #include "base/strings/string_split.h"
8 #include "base/values.h"
9 #include "sync/protocol/app_notification_specifics.pb.h"
10 #include "sync/protocol/app_setting_specifics.pb.h"
11 #include "sync/protocol/app_specifics.pb.h"
12 #include "sync/protocol/autofill_specifics.pb.h"
13 #include "sync/protocol/bookmark_specifics.pb.h"
14 #include "sync/protocol/extension_setting_specifics.pb.h"
15 #include "sync/protocol/extension_specifics.pb.h"
16 #include "sync/protocol/nigori_specifics.pb.h"
17 #include "sync/protocol/password_specifics.pb.h"
18 #include "sync/protocol/preference_specifics.pb.h"
19 #include "sync/protocol/search_engine_specifics.pb.h"
20 #include "sync/protocol/session_specifics.pb.h"
21 #include "sync/protocol/sync.pb.h"
22 #include "sync/protocol/theme_specifics.pb.h"
23 #include "sync/protocol/typed_url_specifics.pb.h"
24 #include "sync/syncable/syncable_proto_util.h"
28 void AddDefaultFieldValue(ModelType datatype
,
29 sync_pb::EntitySpecifics
* specifics
) {
30 if (!ProtocolTypes().Has(datatype
)) {
31 NOTREACHED() << "Only protocol types have field values.";
36 specifics
->mutable_bookmark();
39 specifics
->mutable_password();
42 specifics
->mutable_preference();
45 specifics
->mutable_autofill();
47 case AUTOFILL_PROFILE
:
48 specifics
->mutable_autofill_profile();
51 specifics
->mutable_theme();
54 specifics
->mutable_typed_url();
57 specifics
->mutable_extension();
60 specifics
->mutable_nigori();
63 specifics
->mutable_search_engine();
66 specifics
->mutable_session();
69 specifics
->mutable_app();
72 specifics
->mutable_app_setting();
74 case EXTENSION_SETTINGS
:
75 specifics
->mutable_extension_setting();
77 case APP_NOTIFICATIONS
:
78 specifics
->mutable_app_notification();
80 case HISTORY_DELETE_DIRECTIVES
:
81 specifics
->mutable_history_delete_directive();
83 case SYNCED_NOTIFICATIONS
:
84 specifics
->mutable_synced_notification();
87 specifics
->mutable_device_info();
90 specifics
->mutable_experiments();
92 case PRIORITY_PREFERENCES
:
93 specifics
->mutable_priority_preference();
96 specifics
->mutable_dictionary();
99 specifics
->mutable_favicon_image();
101 case FAVICON_TRACKING
:
102 specifics
->mutable_favicon_tracking();
105 NOTREACHED() << "No known extension for model type.";
109 ModelType
GetModelTypeFromSpecificsFieldNumber(int field_number
) {
110 ModelTypeSet protocol_types
= ProtocolTypes();
111 for (ModelTypeSet::Iterator iter
= protocol_types
.First(); iter
.Good();
113 if (GetSpecificsFieldNumberFromModelType(iter
.Get()) == field_number
)
119 int GetSpecificsFieldNumberFromModelType(ModelType model_type
) {
120 if (!ProtocolTypes().Has(model_type
)) {
121 NOTREACHED() << "Only protocol types have field values.";
124 switch (model_type
) {
126 return sync_pb::EntitySpecifics::kBookmarkFieldNumber
;
129 return sync_pb::EntitySpecifics::kPasswordFieldNumber
;
132 return sync_pb::EntitySpecifics::kPreferenceFieldNumber
;
135 return sync_pb::EntitySpecifics::kAutofillFieldNumber
;
137 case AUTOFILL_PROFILE
:
138 return sync_pb::EntitySpecifics::kAutofillProfileFieldNumber
;
141 return sync_pb::EntitySpecifics::kThemeFieldNumber
;
144 return sync_pb::EntitySpecifics::kTypedUrlFieldNumber
;
147 return sync_pb::EntitySpecifics::kExtensionFieldNumber
;
150 return sync_pb::EntitySpecifics::kNigoriFieldNumber
;
153 return sync_pb::EntitySpecifics::kSearchEngineFieldNumber
;
156 return sync_pb::EntitySpecifics::kSessionFieldNumber
;
159 return sync_pb::EntitySpecifics::kAppFieldNumber
;
162 return sync_pb::EntitySpecifics::kAppSettingFieldNumber
;
164 case EXTENSION_SETTINGS
:
165 return sync_pb::EntitySpecifics::kExtensionSettingFieldNumber
;
167 case APP_NOTIFICATIONS
:
168 return sync_pb::EntitySpecifics::kAppNotificationFieldNumber
;
170 case HISTORY_DELETE_DIRECTIVES
:
171 return sync_pb::EntitySpecifics::kHistoryDeleteDirectiveFieldNumber
;
172 case SYNCED_NOTIFICATIONS
:
173 return sync_pb::EntitySpecifics::kSyncedNotificationFieldNumber
;
175 return sync_pb::EntitySpecifics::kDeviceInfoFieldNumber
;
178 return sync_pb::EntitySpecifics::kExperimentsFieldNumber
;
180 case PRIORITY_PREFERENCES
:
181 return sync_pb::EntitySpecifics::kPriorityPreferenceFieldNumber
;
184 return sync_pb::EntitySpecifics::kDictionaryFieldNumber
;
187 return sync_pb::EntitySpecifics::kFaviconImageFieldNumber
;
188 case FAVICON_TRACKING
:
189 return sync_pb::EntitySpecifics::kFaviconTrackingFieldNumber
;
191 NOTREACHED() << "No known extension for model type.";
194 NOTREACHED() << "Needed for linux_keep_shadow_stacks because of "
195 << "http://gcc.gnu.org/bugzilla/show_bug.cgi?id=20681";
199 FullModelTypeSet
ToFullModelTypeSet(ModelTypeSet in
) {
200 FullModelTypeSet out
;
201 for (ModelTypeSet::Iterator i
= in
.First(); i
.Good(); i
.Inc()) {
207 // Note: keep this consistent with GetModelType in entry.cc!
208 ModelType
GetModelType(const sync_pb::SyncEntity
& sync_entity
) {
209 DCHECK(!IsRoot(sync_entity
)); // Root shouldn't ever go over the wire.
211 if (sync_entity
.deleted())
214 // Backwards compatibility with old (pre-specifics) protocol.
215 if (sync_entity
.has_bookmarkdata())
218 ModelType specifics_type
= GetModelTypeFromSpecifics(sync_entity
.specifics());
219 if (specifics_type
!= UNSPECIFIED
)
220 return specifics_type
;
222 // Loose check for server-created top-level folders that aren't
223 // bound to a particular model type.
224 if (!sync_entity
.server_defined_unique_tag().empty() &&
225 IsFolder(sync_entity
)) {
226 return TOP_LEVEL_FOLDER
;
229 // This is an item of a datatype we can't understand. Maybe it's
230 // from the future? Either we mis-encoded the object, or the
231 // server sent us entries it shouldn't have.
232 NOTREACHED() << "Unknown datatype in sync proto.";
236 ModelType
GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics
& specifics
) {
237 if (specifics
.has_bookmark())
240 if (specifics
.has_password())
243 if (specifics
.has_preference())
246 if (specifics
.has_autofill())
249 if (specifics
.has_autofill_profile())
250 return AUTOFILL_PROFILE
;
252 if (specifics
.has_theme())
255 if (specifics
.has_typed_url())
258 if (specifics
.has_extension())
261 if (specifics
.has_nigori())
264 if (specifics
.has_app())
267 if (specifics
.has_search_engine())
268 return SEARCH_ENGINES
;
270 if (specifics
.has_session())
273 if (specifics
.has_app_setting())
276 if (specifics
.has_extension_setting())
277 return EXTENSION_SETTINGS
;
279 if (specifics
.has_app_notification())
280 return APP_NOTIFICATIONS
;
282 if (specifics
.has_history_delete_directive())
283 return HISTORY_DELETE_DIRECTIVES
;
285 if (specifics
.has_synced_notification())
286 return SYNCED_NOTIFICATIONS
;
288 if (specifics
.has_device_info())
291 if (specifics
.has_experiments())
294 if (specifics
.has_priority_preference())
295 return PRIORITY_PREFERENCES
;
297 if (specifics
.has_dictionary())
300 if (specifics
.has_favicon_image())
301 return FAVICON_IMAGES
;
303 if (specifics
.has_favicon_tracking())
304 return FAVICON_TRACKING
;
309 ModelTypeSet
ProtocolTypes() {
310 ModelTypeSet set
= ModelTypeSet::All();
311 set
.RemoveAll(ProxyTypes());
315 ModelTypeSet
UserTypes() {
317 // TODO(sync): We should be able to build the actual enumset's internal
318 // bitset value here at compile time, rather than performing an iteration
320 for (int i
= FIRST_USER_MODEL_TYPE
; i
<= LAST_USER_MODEL_TYPE
; ++i
) {
321 set
.Put(ModelTypeFromInt(i
));
326 ModelTypeSet
UserSelectableTypes() {
328 // Although the order doesn't technically matter here, it's clearer to keep
329 // these in the same order as their definition in the ModelType enum.
331 set
.Put(PREFERENCES
);;
342 bool IsUserSelectableType(ModelType model_type
) {
343 return UserSelectableTypes().Has(model_type
);
346 ModelTypeSet
EncryptableUserTypes() {
347 ModelTypeSet encryptable_user_types
= UserTypes();
348 // We never encrypt history delete directives.
349 encryptable_user_types
.Remove(HISTORY_DELETE_DIRECTIVES
);
350 // Synced notifications are not encrypted since the server must see changes.
351 encryptable_user_types
.Remove(SYNCED_NOTIFICATIONS
);
352 // Priority preferences are not encrypted because they might be synced before
353 // encryption is ready.
354 encryptable_user_types
.RemoveAll(PriorityUserTypes());
355 // Proxy types have no sync representation and are therefore not encrypted.
356 // Note however that proxy types map to one or more protocol types, which
357 // may or may not be encrypted themselves.
358 encryptable_user_types
.RemoveAll(ProxyTypes());
359 return encryptable_user_types
;
362 ModelTypeSet
PriorityUserTypes() {
363 return ModelTypeSet(PRIORITY_PREFERENCES
);
366 ModelTypeSet
ControlTypes() {
368 // TODO(sync): We should be able to build the actual enumset's internal
369 // bitset value here at compile time, rather than performing an iteration
371 for (int i
= FIRST_CONTROL_MODEL_TYPE
; i
<= LAST_CONTROL_MODEL_TYPE
; ++i
) {
372 set
.Put(ModelTypeFromInt(i
));
378 ModelTypeSet
ProxyTypes() {
384 bool IsControlType(ModelType model_type
) {
385 return ControlTypes().Has(model_type
);
388 const char* ModelTypeToString(ModelType model_type
) {
389 // This is used in serialization routines as well as for displaying debug
390 // information. Do not attempt to change these string values unless you know
391 // what you're doing.
392 switch (model_type
) {
393 case TOP_LEVEL_FOLDER
:
394 return "Top Level Folder";
396 return "Unspecified";
400 return "Preferences";
412 return "Encryption keys";
414 return "Search Engines";
419 case AUTOFILL_PROFILE
:
420 return "Autofill Profiles";
422 return "App settings";
423 case EXTENSION_SETTINGS
:
424 return "Extension settings";
425 case APP_NOTIFICATIONS
:
426 return "App Notifications";
427 case HISTORY_DELETE_DIRECTIVES
:
428 return "History Delete Directives";
429 case SYNCED_NOTIFICATIONS
:
430 return "Synced Notifications";
432 return "Device Info";
434 return "Experiments";
435 case PRIORITY_PREFERENCES
:
436 return "Priority Preferences";
440 return "Favicon Images";
441 case FAVICON_TRACKING
:
442 return "Favicon Tracking";
448 NOTREACHED() << "No known extension for model type.";
452 // The normal rules about histograms apply here. Always append to the bottom of
453 // the list, and be careful to not reuse integer values that have already been
454 // assigned. Don't forget to update histograms.xml when you make changes to
456 int ModelTypeToHistogramInt(ModelType model_type
) {
457 switch (model_type
) {
460 case TOP_LEVEL_FOLDER
:
468 case AUTOFILL_PROFILE
:
486 case EXTENSION_SETTINGS
:
488 case APP_NOTIFICATIONS
:
490 case HISTORY_DELETE_DIRECTIVES
:
498 case SYNCED_NOTIFICATIONS
:
500 case PRIORITY_PREFERENCES
:
506 case FAVICON_TRACKING
:
510 // Silence a compiler warning.
511 case MODEL_TYPE_COUNT
:
517 base::StringValue
* ModelTypeToValue(ModelType model_type
) {
518 if (model_type
>= FIRST_REAL_MODEL_TYPE
) {
519 return new base::StringValue(ModelTypeToString(model_type
));
520 } else if (model_type
== TOP_LEVEL_FOLDER
) {
521 return new base::StringValue("Top-level folder");
522 } else if (model_type
== UNSPECIFIED
) {
523 return new base::StringValue("Unspecified");
526 return new base::StringValue(std::string());
529 ModelType
ModelTypeFromValue(const base::Value
& value
) {
530 if (value
.IsType(base::Value::TYPE_STRING
)) {
532 CHECK(value
.GetAsString(&result
));
533 return ModelTypeFromString(result
);
534 } else if (value
.IsType(base::Value::TYPE_INTEGER
)) {
536 CHECK(value
.GetAsInteger(&result
));
537 return ModelTypeFromInt(result
);
539 NOTREACHED() << "Unsupported value type: " << value
.GetType();
544 ModelType
ModelTypeFromString(const std::string
& model_type_string
) {
545 if (model_type_string
== "Bookmarks")
547 else if (model_type_string
== "Preferences")
549 else if (model_type_string
== "Passwords")
551 else if (model_type_string
== "Autofill")
553 else if (model_type_string
== "Autofill Profiles")
554 return AUTOFILL_PROFILE
;
555 else if (model_type_string
== "Themes")
557 else if (model_type_string
== "Typed URLs")
559 else if (model_type_string
== "Extensions")
561 else if (model_type_string
== "Encryption keys")
563 else if (model_type_string
== "Search Engines")
564 return SEARCH_ENGINES
;
565 else if (model_type_string
== "Sessions")
567 else if (model_type_string
== "Apps")
569 else if (model_type_string
== "App settings")
571 else if (model_type_string
== "Extension settings")
572 return EXTENSION_SETTINGS
;
573 else if (model_type_string
== "App Notifications")
574 return APP_NOTIFICATIONS
;
575 else if (model_type_string
== "History Delete Directives")
576 return HISTORY_DELETE_DIRECTIVES
;
577 else if (model_type_string
== "Synced Notifications")
578 return SYNCED_NOTIFICATIONS
;
579 else if (model_type_string
== "Device Info")
581 else if (model_type_string
== "Experiments")
583 else if (model_type_string
== "Priority Preferences")
584 return PRIORITY_PREFERENCES
;
585 else if (model_type_string
== "Dictionary")
587 else if (model_type_string
== "Favicon Images")
588 return FAVICON_IMAGES
;
589 else if (model_type_string
== "Favicon Tracking")
590 return FAVICON_TRACKING
;
591 else if (model_type_string
== "Tabs")
594 NOTREACHED() << "No known model type corresponding to "
595 << model_type_string
<< ".";
599 std::string
ModelTypeSetToString(ModelTypeSet model_types
) {
601 for (ModelTypeSet::Iterator it
= model_types
.First(); it
.Good(); it
.Inc()) {
602 if (!result
.empty()) {
605 result
+= ModelTypeToString(it
.Get());
610 base::ListValue
* ModelTypeSetToValue(ModelTypeSet model_types
) {
611 base::ListValue
* value
= new base::ListValue();
612 for (ModelTypeSet::Iterator it
= model_types
.First(); it
.Good(); it
.Inc()) {
613 value
->Append(new base::StringValue(ModelTypeToString(it
.Get())));
618 ModelTypeSet
ModelTypeSetFromValue(const base::ListValue
& value
) {
620 for (base::ListValue::const_iterator i
= value
.begin();
621 i
!= value
.end(); ++i
) {
622 result
.Put(ModelTypeFromValue(**i
));
627 // TODO(zea): remove all hardcoded tags in model associators and have them use
629 // NOTE: Proxy types should return empty strings (so that we don't NOTREACHED
630 // in tests when we verify they have no root node).
631 std::string
ModelTypeToRootTag(ModelType type
) {
634 return "google_chrome_bookmarks";
636 return "google_chrome_preferences";
638 return "google_chrome_passwords";
640 return "google_chrome_autofill";
642 return "google_chrome_themes";
644 return "google_chrome_typed_urls";
646 return "google_chrome_extensions";
648 return "google_chrome_nigori";
650 return "google_chrome_search_engines";
652 return "google_chrome_sessions";
654 return "google_chrome_apps";
655 case AUTOFILL_PROFILE
:
656 return "google_chrome_autofill_profiles";
658 return "google_chrome_app_settings";
659 case EXTENSION_SETTINGS
:
660 return "google_chrome_extension_settings";
661 case APP_NOTIFICATIONS
:
662 return "google_chrome_app_notifications";
663 case HISTORY_DELETE_DIRECTIVES
:
664 return "google_chrome_history_delete_directives";
665 case SYNCED_NOTIFICATIONS
:
666 return "google_chrome_synced_notifications";
668 return "google_chrome_device_info";
670 return "google_chrome_experiments";
671 case PRIORITY_PREFERENCES
:
672 return "google_chrome_priority_preferences";
674 return "google_chrome_dictionary";
676 return "google_chrome_favicon_images";
677 case FAVICON_TRACKING
:
678 return "google_chrome_favicon_tracking";
680 return std::string();
684 NOTREACHED() << "No known extension for model type.";
688 // TODO(akalin): Figure out a better way to do these mappings.
689 // Note: Do not include proxy types in this list. They should never receive
690 // or trigger notifications.
692 const char kBookmarkNotificationType
[] = "BOOKMARK";
693 const char kPreferenceNotificationType
[] = "PREFERENCE";
694 const char kPasswordNotificationType
[] = "PASSWORD";
695 const char kAutofillNotificationType
[] = "AUTOFILL";
696 const char kThemeNotificationType
[] = "THEME";
697 const char kTypedUrlNotificationType
[] = "TYPED_URL";
698 const char kExtensionNotificationType
[] = "EXTENSION";
699 const char kExtensionSettingNotificationType
[] = "EXTENSION_SETTING";
700 const char kNigoriNotificationType
[] = "NIGORI";
701 const char kAppSettingNotificationType
[] = "APP_SETTING";
702 const char kAppNotificationType
[] = "APP";
703 const char kSearchEngineNotificationType
[] = "SEARCH_ENGINE";
704 const char kSessionNotificationType
[] = "SESSION";
705 const char kAutofillProfileNotificationType
[] = "AUTOFILL_PROFILE";
706 const char kAppNotificationNotificationType
[] = "APP_NOTIFICATION";
707 const char kHistoryDeleteDirectiveNotificationType
[] =
708 "HISTORY_DELETE_DIRECTIVE";
709 const char kSyncedNotificationType
[] = "SYNCED_NOTIFICATION";
710 const char kDeviceInfoNotificationType
[] = "DEVICE_INFO";
711 const char kExperimentsNotificationType
[] = "EXPERIMENTS";
712 const char kPriorityPreferenceNotificationType
[] = "PRIORITY_PREFERENCE";
713 const char kDictionaryNotificationType
[] = "DICTIONARY";
714 const char kFaviconImageNotificationType
[] = "FAVICON_IMAGE";
715 const char kFaviconTrackingNotificationType
[] = "FAVICON_TRACKING";
718 bool RealModelTypeToNotificationType(ModelType model_type
,
719 std::string
* notification_type
) {
720 switch (model_type
) {
722 *notification_type
= kBookmarkNotificationType
;
725 *notification_type
= kPreferenceNotificationType
;
728 *notification_type
= kPasswordNotificationType
;
731 *notification_type
= kAutofillNotificationType
;
734 *notification_type
= kThemeNotificationType
;
737 *notification_type
= kTypedUrlNotificationType
;
740 *notification_type
= kExtensionNotificationType
;
743 *notification_type
= kNigoriNotificationType
;
746 *notification_type
= kAppSettingNotificationType
;
749 *notification_type
= kAppNotificationType
;
752 *notification_type
= kSearchEngineNotificationType
;
755 *notification_type
= kSessionNotificationType
;
757 case AUTOFILL_PROFILE
:
758 *notification_type
= kAutofillProfileNotificationType
;
760 case EXTENSION_SETTINGS
:
761 *notification_type
= kExtensionSettingNotificationType
;
763 case APP_NOTIFICATIONS
:
764 *notification_type
= kAppNotificationNotificationType
;
766 case HISTORY_DELETE_DIRECTIVES
:
767 *notification_type
= kHistoryDeleteDirectiveNotificationType
;
769 case SYNCED_NOTIFICATIONS
:
770 *notification_type
= kSyncedNotificationType
;
773 *notification_type
= kDeviceInfoNotificationType
;
776 *notification_type
= kExperimentsNotificationType
;
778 case PRIORITY_PREFERENCES
:
779 *notification_type
= kPriorityPreferenceNotificationType
;
782 *notification_type
= kDictionaryNotificationType
;
785 *notification_type
= kFaviconImageNotificationType
;
787 case FAVICON_TRACKING
:
788 *notification_type
= kFaviconTrackingNotificationType
;
793 notification_type
->clear();
797 bool NotificationTypeToRealModelType(const std::string
& notification_type
,
798 ModelType
* model_type
) {
799 if (notification_type
== kBookmarkNotificationType
) {
800 *model_type
= BOOKMARKS
;
802 } else if (notification_type
== kPreferenceNotificationType
) {
803 *model_type
= PREFERENCES
;
805 } else if (notification_type
== kPasswordNotificationType
) {
806 *model_type
= PASSWORDS
;
808 } else if (notification_type
== kAutofillNotificationType
) {
809 *model_type
= AUTOFILL
;
811 } else if (notification_type
== kThemeNotificationType
) {
812 *model_type
= THEMES
;
814 } else if (notification_type
== kTypedUrlNotificationType
) {
815 *model_type
= TYPED_URLS
;
817 } else if (notification_type
== kExtensionNotificationType
) {
818 *model_type
= EXTENSIONS
;
820 } else if (notification_type
== kNigoriNotificationType
) {
821 *model_type
= NIGORI
;
823 } else if (notification_type
== kAppNotificationType
) {
826 } else if (notification_type
== kSearchEngineNotificationType
) {
827 *model_type
= SEARCH_ENGINES
;
829 } else if (notification_type
== kSessionNotificationType
) {
830 *model_type
= SESSIONS
;
832 } else if (notification_type
== kAutofillProfileNotificationType
) {
833 *model_type
= AUTOFILL_PROFILE
;
835 } else if (notification_type
== kAppSettingNotificationType
) {
836 *model_type
= APP_SETTINGS
;
838 } else if (notification_type
== kExtensionSettingNotificationType
) {
839 *model_type
= EXTENSION_SETTINGS
;
841 } else if (notification_type
== kAppNotificationNotificationType
) {
842 *model_type
= APP_NOTIFICATIONS
;
844 } else if (notification_type
== kHistoryDeleteDirectiveNotificationType
) {
845 *model_type
= HISTORY_DELETE_DIRECTIVES
;
847 } else if (notification_type
== kSyncedNotificationType
) {
848 *model_type
= SYNCED_NOTIFICATIONS
;
850 } else if (notification_type
== kDeviceInfoNotificationType
) {
851 *model_type
= DEVICE_INFO
;
853 } else if (notification_type
== kExperimentsNotificationType
) {
854 *model_type
= EXPERIMENTS
;
856 } else if (notification_type
== kPriorityPreferenceNotificationType
) {
857 *model_type
= PRIORITY_PREFERENCES
;
859 } else if (notification_type
== kDictionaryNotificationType
) {
860 *model_type
= DICTIONARY
;
862 } else if (notification_type
== kFaviconImageNotificationType
) {
863 *model_type
= FAVICON_IMAGES
;
865 } else if (notification_type
== kFaviconTrackingNotificationType
) {
866 *model_type
= FAVICON_TRACKING
;
869 *model_type
= UNSPECIFIED
;
873 bool IsRealDataType(ModelType model_type
) {
874 return model_type
>= FIRST_REAL_MODEL_TYPE
&& model_type
< MODEL_TYPE_COUNT
;
877 bool IsActOnceDataType(ModelType model_type
) {
878 return model_type
== HISTORY_DELETE_DIRECTIVES
;
881 } // namespace syncer