1 // Copyright 2013 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 "chrome/browser/ui/app_list/app_list_syncable_service.h"
7 #include "base/command_line.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/apps/drive/drive_app_provider.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/app_list/app_list_prefs.h"
13 #include "chrome/browser/ui/app_list/app_list_service.h"
14 #include "chrome/browser/ui/app_list/extension_app_item.h"
15 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
16 #include "chrome/browser/ui/app_list/model_pref_updater.h"
17 #include "chrome/browser/ui/host_desktop.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/browser/uninstall_reason.h"
24 #include "extensions/common/constants.h"
25 #include "sync/api/sync_change_processor.h"
26 #include "sync/api/sync_data.h"
27 #include "sync/api/sync_merge_result.h"
28 #include "sync/protocol/sync.pb.h"
29 #include "ui/app_list/app_list_folder_item.h"
30 #include "ui/app_list/app_list_item.h"
31 #include "ui/app_list/app_list_model.h"
32 #include "ui/app_list/app_list_model_observer.h"
33 #include "ui/app_list/app_list_switches.h"
34 #include "ui/base/l10n/l10n_util.h"
36 #if defined(OS_CHROMEOS)
37 #include "chrome/browser/chromeos/file_manager/app_id.h"
38 #include "chrome/browser/chromeos/genius_app/app_id.h"
41 using syncer::SyncChange
;
47 const char kOemFolderId
[] = "ddb1da55-d478-4243-8642-56d3041f0263";
49 // Prefix for a sync id of a Drive app. Drive app ids are in a different
50 // format and have to be used because a Drive app could have only an URL
51 // without a matching Chrome app. To differentiate the Drive app id from
52 // Chrome app ids, this prefix will be added to create the sync item id
53 // for a Drive app item.
54 const char kDriveAppSyncIdPrefix
[] = "drive-app-";
56 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics
& specifics
,
57 AppListSyncableService::SyncItem
* item
) {
58 DCHECK_EQ(item
->item_id
, specifics
.item_id());
59 item
->item_type
= specifics
.item_type();
60 item
->item_name
= specifics
.item_name();
61 item
->parent_id
= specifics
.parent_id();
62 if (!specifics
.item_ordinal().empty())
63 item
->item_ordinal
= syncer::StringOrdinal(specifics
.item_ordinal());
66 bool UpdateSyncItemFromAppItem(const AppListItem
* app_item
,
67 AppListSyncableService::SyncItem
* sync_item
) {
68 DCHECK_EQ(sync_item
->item_id
, app_item
->id());
70 if (app_list::switches::IsFolderUIEnabled() &&
71 sync_item
->parent_id
!= app_item
->folder_id()) {
72 sync_item
->parent_id
= app_item
->folder_id();
75 if (sync_item
->item_name
!= app_item
->name()) {
76 sync_item
->item_name
= app_item
->name();
79 if (!sync_item
->item_ordinal
.IsValid() ||
80 !app_item
->position().Equals(sync_item
->item_ordinal
)) {
81 sync_item
->item_ordinal
= app_item
->position();
87 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem
* item
,
88 sync_pb::AppListSpecifics
* specifics
) {
90 specifics
->set_item_id(item
->item_id
);
91 specifics
->set_item_type(item
->item_type
);
92 specifics
->set_item_name(item
->item_name
);
93 specifics
->set_parent_id(item
->parent_id
);
94 if (item
->item_ordinal
.IsValid())
95 specifics
->set_item_ordinal(item
->item_ordinal
.ToInternalValue());
98 syncer::SyncData
GetSyncDataFromSyncItem(
99 const AppListSyncableService::SyncItem
* item
) {
100 sync_pb::EntitySpecifics specifics
;
101 GetSyncSpecificsFromSyncItem(item
, specifics
.mutable_app_list());
102 return syncer::SyncData::CreateLocalData(item
->item_id
,
107 bool AppIsDefault(ExtensionService
* service
, const std::string
& id
) {
108 return service
&& extensions::ExtensionPrefs::Get(service
->profile())
109 ->WasInstalledByDefault(id
);
112 bool IsUnRemovableDefaultApp(const std::string
& id
) {
113 if (id
== extension_misc::kChromeAppId
||
114 id
== extensions::kWebStoreAppId
)
116 #if defined(OS_CHROMEOS)
117 if (id
== file_manager::kFileManagerAppId
|| id
== genius_app::kGeniusAppId
)
123 void UninstallExtension(ExtensionService
* service
, const std::string
& id
) {
124 if (service
&& service
->GetInstalledExtension(id
)) {
125 service
->UninstallExtension(id
,
126 extensions::UNINSTALL_REASON_SYNC
,
127 base::Bind(&base::DoNothing
),
132 bool GetAppListItemType(AppListItem
* item
,
133 sync_pb::AppListSpecifics::AppListItemType
* type
) {
134 const char* item_type
= item
->GetItemType();
135 if (item_type
== ExtensionAppItem::kItemType
) {
136 *type
= sync_pb::AppListSpecifics::TYPE_APP
;
137 } else if (item_type
== AppListFolderItem::kItemType
) {
138 *type
= sync_pb::AppListSpecifics::TYPE_FOLDER
;
140 LOG(ERROR
) << "Unrecognized model type: " << item_type
;
146 bool IsDriveAppSyncId(const std::string
& sync_id
) {
147 return StartsWithASCII(sync_id
, kDriveAppSyncIdPrefix
, true);
150 std::string
GetDriveAppSyncId(const std::string
& drive_app_id
) {
151 return kDriveAppSyncIdPrefix
+ drive_app_id
;
154 std::string
GetDriveAppIdFromSyncId(const std::string
& sync_id
) {
155 if (!IsDriveAppSyncId(sync_id
))
156 return std::string();
157 return sync_id
.substr(strlen(kDriveAppSyncIdPrefix
));
162 // AppListSyncableService::SyncItem
164 AppListSyncableService::SyncItem::SyncItem(
165 const std::string
& id
,
166 sync_pb::AppListSpecifics::AppListItemType type
)
171 AppListSyncableService::SyncItem::~SyncItem() {
174 // AppListSyncableService::ModelObserver
176 class AppListSyncableService::ModelObserver
: public AppListModelObserver
{
178 explicit ModelObserver(AppListSyncableService
* owner
)
181 DVLOG(2) << owner_
<< ": ModelObserver Added";
182 owner_
->GetModel()->AddObserver(this);
185 ~ModelObserver() override
{
186 owner_
->GetModel()->RemoveObserver(this);
187 DVLOG(2) << owner_
<< ": ModelObserver Removed";
191 // AppListModelObserver
192 void OnAppListItemAdded(AppListItem
* item
) override
{
193 DCHECK(!adding_item_
);
194 adding_item_
= item
; // Ignore updates while adding an item.
195 VLOG(2) << owner_
<< " OnAppListItemAdded: " << item
->ToDebugString();
196 owner_
->AddOrUpdateFromSyncItem(item
);
200 void OnAppListItemWillBeDeleted(AppListItem
* item
) override
{
201 DCHECK(!adding_item_
);
202 VLOG(2) << owner_
<< " OnAppListItemDeleted: " << item
->ToDebugString();
203 // Don't sync folder removal in case the folder still exists on another
204 // device (e.g. with device specific items in it). Empty folders will be
205 // deleted when the last item is removed (in PruneEmptySyncFolders()).
206 if (item
->GetItemType() == AppListFolderItem::kItemType
)
208 owner_
->RemoveSyncItem(item
->id());
211 void OnAppListItemUpdated(AppListItem
* item
) override
{
213 // Adding an item may trigger update notifications which should be
215 DCHECK_EQ(adding_item_
, item
);
218 VLOG(2) << owner_
<< " OnAppListItemUpdated: " << item
->ToDebugString();
219 owner_
->UpdateSyncItem(item
);
222 AppListSyncableService
* owner_
;
223 AppListItem
* adding_item_
; // Unowned pointer to item being added.
225 DISALLOW_COPY_AND_ASSIGN(ModelObserver
);
228 // AppListSyncableService
230 AppListSyncableService::AppListSyncableService(
232 extensions::ExtensionSystem
* extension_system
)
234 extension_system_(extension_system
),
235 model_(new AppListModel
),
236 initial_sync_data_processed_(false),
237 first_app_list_sync_(true) {
238 if (!extension_system
) {
239 LOG(ERROR
) << "AppListSyncableService created with no ExtensionSystem";
244 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME
);
247 AppListSyncableService::~AppListSyncableService() {
249 model_observer_
.reset();
250 model_pref_updater_
.reset();
252 STLDeleteContainerPairSecondPointers(sync_items_
.begin(), sync_items_
.end());
255 void AppListSyncableService::BuildModel() {
256 // TODO(calamity): make this a DCHECK after a dev channel release.
257 CHECK(extension_system_
->extension_service() &&
258 extension_system_
->extension_service()->is_ready());
259 // For now, use the AppListControllerDelegate associated with the native
260 // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller
261 // dependency and move the dependent methods from AppListControllerDelegate
262 // to an extension service delegate associated with this class.
263 AppListControllerDelegate
* controller
= NULL
;
264 AppListService
* service
=
265 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE
);
267 controller
= service
->GetControllerDelegate();
268 apps_builder_
.reset(new ExtensionAppModelBuilder(controller
));
270 if (app_list::switches::IsAppListSyncEnabled()) {
271 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
273 apps_builder_
->InitializeWithService(this, model_
.get());
275 VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
276 apps_builder_
->InitializeWithProfile(profile_
, model_
.get());
279 model_pref_updater_
.reset(
280 new ModelPrefUpdater(AppListPrefs::Get(profile_
), model_
.get()));
282 if (app_list::switches::IsDriveAppsInAppListEnabled())
283 drive_app_provider_
.reset(new DriveAppProvider(profile_
, this));
286 size_t AppListSyncableService::GetNumSyncItemsForTest() {
287 // If the model isn't built yet, there will be no sync items.
290 return sync_items_
.size();
293 void AppListSyncableService::ResetDriveAppProviderForTest() {
294 drive_app_provider_
.reset();
297 void AppListSyncableService::Shutdown() {
298 // DriveAppProvider touches other KeyedServices in its dtor and needs be
299 // released in shutdown stage.
300 drive_app_provider_
.reset();
303 void AppListSyncableService::TrackUninstalledDriveApp(
304 const std::string
& drive_app_id
) {
305 const std::string sync_id
= GetDriveAppSyncId(drive_app_id
);
306 SyncItem
* sync_item
= FindSyncItem(sync_id
);
308 DCHECK_EQ(sync_item
->item_type
,
309 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
);
313 sync_item
= CreateSyncItem(
314 sync_id
, sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
);
315 SendSyncChange(sync_item
, SyncChange::ACTION_ADD
);
318 void AppListSyncableService::UntrackUninstalledDriveApp(
319 const std::string
& drive_app_id
) {
320 const std::string sync_id
= GetDriveAppSyncId(drive_app_id
);
321 SyncItem
* sync_item
= FindSyncItem(sync_id
);
325 DCHECK_EQ(sync_item
->item_type
,
326 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
);
327 DeleteSyncItem(sync_item
);
330 const AppListSyncableService::SyncItem
*
331 AppListSyncableService::GetSyncItem(const std::string
& id
) const {
332 SyncItemMap::const_iterator iter
= sync_items_
.find(id
);
333 if (iter
!= sync_items_
.end())
338 void AppListSyncableService::SetOemFolderName(const std::string
& name
) {
339 oem_folder_name_
= name
;
340 AppListFolderItem
* oem_folder
= model_
->FindFolderItem(kOemFolderId
);
342 model_
->SetItemName(oem_folder
, oem_folder_name_
);
345 AppListModel
* AppListSyncableService::GetModel() {
352 void AppListSyncableService::AddItem(scoped_ptr
<AppListItem
> app_item
) {
353 SyncItem
* sync_item
= FindOrAddSyncItem(app_item
.get());
355 return; // Item is not valid.
357 std::string folder_id
;
358 if (app_list::switches::IsFolderUIEnabled()) {
359 if (AppIsOem(app_item
->id())) {
360 folder_id
= FindOrCreateOemFolder();
361 VLOG_IF(2, !folder_id
.empty())
362 << this << ": AddItem to OEM folder: " << sync_item
->ToString();
364 folder_id
= sync_item
->parent_id
;
367 VLOG(2) << this << ": AddItem: " << sync_item
->ToString()
368 << " Folder: '" << folder_id
<< "'";
369 model_
->AddItemToFolder(app_item
.Pass(), folder_id
);
372 AppListSyncableService::SyncItem
* AppListSyncableService::FindOrAddSyncItem(
373 AppListItem
* app_item
) {
374 const std::string
& item_id
= app_item
->id();
375 if (item_id
.empty()) {
376 LOG(ERROR
) << "AppListItem item with empty ID";
379 SyncItem
* sync_item
= FindSyncItem(item_id
);
381 // If there is an existing, non-REMOVE_DEFAULT entry, return it.
382 if (sync_item
->item_type
!=
383 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
384 DVLOG(2) << this << ": AddItem already exists: " << sync_item
->ToString();
388 if (RemoveDefaultApp(app_item
, sync_item
))
391 // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
392 // App entry can be added.
395 return CreateSyncItemFromAppItem(app_item
);
398 AppListSyncableService::SyncItem
*
399 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem
* app_item
) {
400 sync_pb::AppListSpecifics::AppListItemType type
;
401 if (!GetAppListItemType(app_item
, &type
))
403 VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item
->ToDebugString();
404 SyncItem
* sync_item
= CreateSyncItem(app_item
->id(), type
);
405 UpdateSyncItemFromAppItem(app_item
, sync_item
);
406 SendSyncChange(sync_item
, SyncChange::ACTION_ADD
);
410 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem
* app_item
) {
411 // Do not create a sync item for the OEM folder here, do that in
412 // ResolveFolderPositions once the position has been resolved.
413 if (app_item
->id() == kOemFolderId
)
416 SyncItem
* sync_item
= FindSyncItem(app_item
->id());
418 UpdateAppItemFromSyncItem(sync_item
, app_item
);
421 CreateSyncItemFromAppItem(app_item
);
424 bool AppListSyncableService::RemoveDefaultApp(AppListItem
* item
,
425 SyncItem
* sync_item
) {
426 CHECK_EQ(sync_item
->item_type
,
427 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
);
429 // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
430 // installed as a Default app, uninstall the app instead of adding it.
431 if (sync_item
->item_type
== sync_pb::AppListSpecifics::TYPE_APP
&&
432 AppIsDefault(extension_system_
->extension_service(), item
->id())) {
433 VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
434 << sync_item
->ToString();
435 UninstallExtension(extension_system_
->extension_service(), item
->id());
439 // Otherwise, we are adding the app as a non-default app (i.e. an app that
440 // was installed by Default and removed is getting installed explicitly by
441 // the user), so delete the REMOVE_DEFAULT_APP.
442 DeleteSyncItem(sync_item
);
446 void AppListSyncableService::DeleteSyncItem(SyncItem
* sync_item
) {
448 VLOG(2) << this << " -> SYNC DELETE: " << sync_item
->ToString();
449 SyncChange
sync_change(FROM_HERE
, SyncChange::ACTION_DELETE
,
450 GetSyncDataFromSyncItem(sync_item
));
451 sync_processor_
->ProcessSyncChanges(
452 FROM_HERE
, syncer::SyncChangeList(1, sync_change
));
454 std::string item_id
= sync_item
->item_id
;
456 sync_items_
.erase(item_id
);
459 void AppListSyncableService::UpdateSyncItem(AppListItem
* app_item
) {
460 SyncItem
* sync_item
= FindSyncItem(app_item
->id());
462 LOG(ERROR
) << "UpdateItem: no sync item: " << app_item
->id();
465 bool changed
= UpdateSyncItemFromAppItem(app_item
, sync_item
);
467 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item
->ToString();
470 SendSyncChange(sync_item
, SyncChange::ACTION_UPDATE
);
473 void AppListSyncableService::RemoveItem(const std::string
& id
) {
475 model_
->DeleteItem(id
);
476 PruneEmptySyncFolders();
479 void AppListSyncableService::RemoveUninstalledItem(const std::string
& id
) {
481 model_
->DeleteUninstalledItem(id
);
482 PruneEmptySyncFolders();
485 void AppListSyncableService::UpdateItem(AppListItem
* app_item
) {
486 // Check to see if the item needs to be moved to/from the OEM folder.
487 if (!app_list::switches::IsFolderUIEnabled())
489 bool is_oem
= AppIsOem(app_item
->id());
490 if (!is_oem
&& app_item
->folder_id() == kOemFolderId
)
491 model_
->MoveItemToFolder(app_item
, "");
492 else if (is_oem
&& app_item
->folder_id() != kOemFolderId
)
493 model_
->MoveItemToFolder(app_item
, kOemFolderId
);
496 void AppListSyncableService::RemoveSyncItem(const std::string
& id
) {
497 VLOG(2) << this << ": RemoveSyncItem: " << id
.substr(0, 8);
498 SyncItemMap::iterator iter
= sync_items_
.find(id
);
499 if (iter
== sync_items_
.end()) {
500 DVLOG(2) << this << " : RemoveSyncItem: No Item.";
504 // Check for existing RemoveDefault sync item.
505 SyncItem
* sync_item
= iter
->second
;
506 sync_pb::AppListSpecifics::AppListItemType type
= sync_item
->item_type
;
507 if (type
== sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
508 // RemoveDefault item exists, just return.
509 DVLOG(2) << this << " : RemoveDefault Item exists.";
513 if (type
== sync_pb::AppListSpecifics::TYPE_APP
&&
514 AppIsDefault(extension_system_
->extension_service(), id
)) {
515 // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
516 // will overwrite any existing entry for the item.
517 VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
518 << sync_item
->item_id
;
519 sync_item
->item_type
= sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
;
520 SendSyncChange(sync_item
, SyncChange::ACTION_UPDATE
);
524 DeleteSyncItem(sync_item
);
527 void AppListSyncableService::ResolveFolderPositions() {
528 if (!app_list::switches::IsFolderUIEnabled())
531 VLOG(1) << "ResolveFolderPositions.";
532 for (SyncItemMap::iterator iter
= sync_items_
.begin();
533 iter
!= sync_items_
.end(); ++iter
) {
534 SyncItem
* sync_item
= iter
->second
;
535 if (sync_item
->item_type
!= sync_pb::AppListSpecifics::TYPE_FOLDER
)
537 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
540 UpdateAppItemFromSyncItem(sync_item
, app_item
);
543 // Move the OEM folder if one exists and we have not synced its position.
544 AppListFolderItem
* oem_folder
= model_
->FindFolderItem(kOemFolderId
);
545 if (oem_folder
&& !FindSyncItem(kOemFolderId
)) {
546 model_
->SetItemPosition(oem_folder
, GetOemFolderPos());
547 VLOG(1) << "Creating new OEM folder sync item: "
548 << oem_folder
->position().ToDebugString();
549 CreateSyncItemFromAppItem(oem_folder
);
553 void AppListSyncableService::PruneEmptySyncFolders() {
554 if (!app_list::switches::IsFolderUIEnabled())
557 std::set
<std::string
> parent_ids
;
558 for (SyncItemMap::iterator iter
= sync_items_
.begin();
559 iter
!= sync_items_
.end(); ++iter
) {
560 parent_ids
.insert(iter
->second
->parent_id
);
562 for (SyncItemMap::iterator iter
= sync_items_
.begin();
563 iter
!= sync_items_
.end(); ) {
564 SyncItem
* sync_item
= (iter
++)->second
;
565 if (sync_item
->item_type
!= sync_pb::AppListSpecifics::TYPE_FOLDER
)
567 if (!ContainsKey(parent_ids
, sync_item
->item_id
))
568 DeleteSyncItem(sync_item
);
572 // AppListSyncableService syncer::SyncableService
574 syncer::SyncMergeResult
AppListSyncableService::MergeDataAndStartSyncing(
575 syncer::ModelType type
,
576 const syncer::SyncDataList
& initial_sync_data
,
577 scoped_ptr
<syncer::SyncChangeProcessor
> sync_processor
,
578 scoped_ptr
<syncer::SyncErrorFactory
> error_handler
) {
579 DCHECK(!sync_processor_
.get());
580 DCHECK(sync_processor
.get());
581 DCHECK(error_handler
.get());
583 // Ensure the model is built.
586 sync_processor_
= sync_processor
.Pass();
587 sync_error_handler_
= error_handler
.Pass();
588 if (switches::IsFolderUIEnabled())
589 model_
->SetFoldersEnabled(true);
591 syncer::SyncMergeResult result
= syncer::SyncMergeResult(type
);
592 result
.set_num_items_before_association(sync_items_
.size());
593 VLOG(1) << this << ": MergeDataAndStartSyncing: "
594 << initial_sync_data
.size();
596 // Copy all sync items to |unsynced_items|.
597 std::set
<std::string
> unsynced_items
;
598 for (SyncItemMap::const_iterator iter
= sync_items_
.begin();
599 iter
!= sync_items_
.end(); ++iter
) {
600 unsynced_items
.insert(iter
->first
);
603 // Create SyncItem entries for initial_sync_data.
604 size_t new_items
= 0, updated_items
= 0;
605 for (syncer::SyncDataList::const_iterator iter
= initial_sync_data
.begin();
606 iter
!= initial_sync_data
.end(); ++iter
) {
607 const syncer::SyncData
& data
= *iter
;
608 const std::string
& item_id
= data
.GetSpecifics().app_list().item_id();
609 const sync_pb::AppListSpecifics
& specifics
= data
.GetSpecifics().app_list();
610 DVLOG(2) << this << " Initial Sync Item: " << item_id
611 << " Type: " << specifics
.item_type();
612 DCHECK_EQ(syncer::APP_LIST
, data
.GetDataType());
613 if (ProcessSyncItemSpecifics(specifics
))
617 if (specifics
.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER
&&
618 !IsUnRemovableDefaultApp(item_id
) &&
619 !AppIsOem(item_id
) &&
620 !AppIsDefault(extension_system_
->extension_service(), item_id
)) {
621 VLOG(2) << "Syncing non-default item: " << item_id
;
622 first_app_list_sync_
= false;
624 unsynced_items
.erase(item_id
);
626 result
.set_num_items_after_association(sync_items_
.size());
627 result
.set_num_items_added(new_items
);
628 result
.set_num_items_deleted(0);
629 result
.set_num_items_modified(updated_items
);
631 // Initial sync data has been processed, it is safe now to add new sync items.
632 initial_sync_data_processed_
= true;
634 // Send unsynced items. Does not affect |result|.
635 syncer::SyncChangeList change_list
;
636 for (std::set
<std::string
>::iterator iter
= unsynced_items
.begin();
637 iter
!= unsynced_items
.end(); ++iter
) {
638 SyncItem
* sync_item
= FindSyncItem(*iter
);
639 // Sync can cause an item to change folders, causing an unsynced folder
640 // item to be removed.
643 VLOG(2) << this << " -> SYNC ADD: " << sync_item
->ToString();
644 change_list
.push_back(SyncChange(FROM_HERE
, SyncChange::ACTION_ADD
,
645 GetSyncDataFromSyncItem(sync_item
)));
647 sync_processor_
->ProcessSyncChanges(FROM_HERE
, change_list
);
649 // Adding items may have created folders without setting their positions
650 // since we haven't started observing the item list yet. Resolve those.
651 ResolveFolderPositions();
653 // Start observing app list model changes.
654 model_observer_
.reset(new ModelObserver(this));
659 void AppListSyncableService::StopSyncing(syncer::ModelType type
) {
660 DCHECK_EQ(type
, syncer::APP_LIST
);
662 sync_processor_
.reset();
663 sync_error_handler_
.reset();
664 model_
->SetFoldersEnabled(false);
667 syncer::SyncDataList
AppListSyncableService::GetAllSyncData(
668 syncer::ModelType type
) const {
669 DCHECK_EQ(syncer::APP_LIST
, type
);
671 VLOG(1) << this << ": GetAllSyncData: " << sync_items_
.size();
672 syncer::SyncDataList list
;
673 for (SyncItemMap::const_iterator iter
= sync_items_
.begin();
674 iter
!= sync_items_
.end(); ++iter
) {
675 VLOG(2) << this << " -> SYNC: " << iter
->second
->ToString();
676 list
.push_back(GetSyncDataFromSyncItem(iter
->second
));
681 syncer::SyncError
AppListSyncableService::ProcessSyncChanges(
682 const tracked_objects::Location
& from_here
,
683 const syncer::SyncChangeList
& change_list
) {
684 if (!sync_processor_
.get()) {
685 return syncer::SyncError(FROM_HERE
,
686 syncer::SyncError::DATATYPE_ERROR
,
687 "App List syncable service is not started.",
691 // Don't observe the model while processing incoming sync changes.
692 model_observer_
.reset();
694 VLOG(1) << this << ": ProcessSyncChanges: " << change_list
.size();
695 for (syncer::SyncChangeList::const_iterator iter
= change_list
.begin();
696 iter
!= change_list
.end(); ++iter
) {
697 const SyncChange
& change
= *iter
;
698 VLOG(2) << this << " Change: "
699 << change
.sync_data().GetSpecifics().app_list().item_id()
700 << " (" << change
.change_type() << ")";
701 if (change
.change_type() == SyncChange::ACTION_ADD
||
702 change
.change_type() == SyncChange::ACTION_UPDATE
) {
703 ProcessSyncItemSpecifics(change
.sync_data().GetSpecifics().app_list());
704 } else if (change
.change_type() == SyncChange::ACTION_DELETE
) {
705 DeleteSyncItemSpecifics(change
.sync_data().GetSpecifics().app_list());
707 LOG(ERROR
) << "Invalid sync change";
711 // Continue observing app list model changes.
712 model_observer_
.reset(new ModelObserver(this));
714 return syncer::SyncError();
717 // AppListSyncableService private
719 bool AppListSyncableService::ProcessSyncItemSpecifics(
720 const sync_pb::AppListSpecifics
& specifics
) {
721 const std::string
& item_id
= specifics
.item_id();
722 if (item_id
.empty()) {
723 LOG(ERROR
) << "AppList item with empty ID";
726 SyncItem
* sync_item
= FindSyncItem(item_id
);
728 // If an item of the same type exists, update it.
729 if (sync_item
->item_type
== specifics
.item_type()) {
730 UpdateSyncItemFromSync(specifics
, sync_item
);
731 ProcessExistingSyncItem(sync_item
);
732 VLOG(2) << this << " <- SYNC UPDATE: " << sync_item
->ToString();
735 // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
736 if (sync_item
->item_type
!=
737 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
&&
738 specifics
.item_type() !=
739 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
740 LOG(ERROR
) << "Synced item type: " << specifics
.item_type()
741 << " != existing sync item type: " << sync_item
->item_type
742 << " Deleting item from model!";
743 model_
->DeleteItem(item_id
);
745 VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
746 << sync_item
->ToString();
748 sync_items_
.erase(item_id
);
751 sync_item
= CreateSyncItem(item_id
, specifics
.item_type());
752 UpdateSyncItemFromSync(specifics
, sync_item
);
753 ProcessNewSyncItem(sync_item
);
754 VLOG(2) << this << " <- SYNC ADD: " << sync_item
->ToString();
758 void AppListSyncableService::ProcessNewSyncItem(SyncItem
* sync_item
) {
759 VLOG(2) << "ProcessNewSyncItem: " << sync_item
->ToString();
760 switch (sync_item
->item_type
) {
761 case sync_pb::AppListSpecifics::TYPE_APP
: {
762 // New apps are added through ExtensionAppModelBuilder.
763 // TODO(stevenjb): Determine how to handle app items in sync that
764 // are not installed (e.g. default / OEM apps).
767 case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
: {
768 VLOG(1) << this << ": Uninstall: " << sync_item
->ToString();
769 if (IsDriveAppSyncId(sync_item
->item_id
)) {
770 if (drive_app_provider_
) {
771 drive_app_provider_
->AddUninstalledDriveAppFromSync(
772 GetDriveAppIdFromSyncId(sync_item
->item_id
));
775 UninstallExtension(extension_system_
->extension_service(),
780 case sync_pb::AppListSpecifics::TYPE_FOLDER
: {
781 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
783 return; // Don't create new folders here, the model will do that.
784 UpdateAppItemFromSyncItem(sync_item
, app_item
);
787 case sync_pb::AppListSpecifics::TYPE_URL
: {
788 // TODO(stevenjb): Implement
789 LOG(WARNING
) << "TYPE_URL not supported";
793 NOTREACHED() << "Unrecognized sync item type: " << sync_item
->ToString();
796 void AppListSyncableService::ProcessExistingSyncItem(SyncItem
* sync_item
) {
797 if (sync_item
->item_type
==
798 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
801 VLOG(2) << "ProcessExistingSyncItem: " << sync_item
->ToString();
802 AppListItem
* app_item
= model_
->FindItem(sync_item
->item_id
);
803 DVLOG(2) << " AppItem: " << app_item
->ToDebugString();
805 LOG(ERROR
) << "Item not found in model: " << sync_item
->ToString();
808 // This is the only place where sync can cause an item to change folders.
809 if (app_list::switches::IsFolderUIEnabled() &&
810 app_item
->folder_id() != sync_item
->parent_id
&&
811 !AppIsOem(app_item
->id())) {
812 VLOG(2) << " Moving Item To Folder: " << sync_item
->parent_id
;
813 model_
->MoveItemToFolder(app_item
, sync_item
->parent_id
);
815 UpdateAppItemFromSyncItem(sync_item
, app_item
);
818 void AppListSyncableService::UpdateAppItemFromSyncItem(
819 const AppListSyncableService::SyncItem
* sync_item
,
820 AppListItem
* app_item
) {
821 VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item
->ToString();
822 if (!app_item
->position().Equals(sync_item
->item_ordinal
))
823 model_
->SetItemPosition(app_item
, sync_item
->item_ordinal
);
824 // Only update the item name if it is a Folder or the name is empty.
825 if (sync_item
->item_name
!= app_item
->name() &&
826 sync_item
->item_id
!= kOemFolderId
&&
827 (app_item
->GetItemType() == AppListFolderItem::kItemType
||
828 app_item
->name().empty())) {
829 model_
->SetItemName(app_item
, sync_item
->item_name
);
833 bool AppListSyncableService::SyncStarted() {
834 if (sync_processor_
.get())
836 if (flare_
.is_null()) {
837 VLOG(1) << this << ": SyncStarted: Flare.";
838 flare_
= sync_start_util::GetFlareForSyncableService(profile_
->GetPath());
839 flare_
.Run(syncer::APP_LIST
);
844 void AppListSyncableService::SendSyncChange(
846 SyncChange::SyncChangeType sync_change_type
) {
847 if (!SyncStarted()) {
848 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
849 << sync_item
->ToString();
852 if (!initial_sync_data_processed_
&&
853 sync_change_type
== SyncChange::ACTION_ADD
) {
854 // This can occur if an initial item is created before its folder item.
855 // A sync item should already exist for the folder, so we do not want to
856 // send an ADD event, since that would trigger a CHECK in the sync code.
857 DCHECK(sync_item
->item_type
== sync_pb::AppListSpecifics::TYPE_FOLDER
);
858 DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: "
859 << sync_item
->ToString();
862 if (sync_change_type
== SyncChange::ACTION_ADD
)
863 VLOG(2) << this << " -> SYNC ADD: " << sync_item
->ToString();
865 VLOG(2) << this << " -> SYNC UPDATE: " << sync_item
->ToString();
866 SyncChange
sync_change(FROM_HERE
, sync_change_type
,
867 GetSyncDataFromSyncItem(sync_item
));
868 sync_processor_
->ProcessSyncChanges(
869 FROM_HERE
, syncer::SyncChangeList(1, sync_change
));
872 AppListSyncableService::SyncItem
*
873 AppListSyncableService::FindSyncItem(const std::string
& item_id
) {
874 SyncItemMap::iterator iter
= sync_items_
.find(item_id
);
875 if (iter
== sync_items_
.end())
880 AppListSyncableService::SyncItem
*
881 AppListSyncableService::CreateSyncItem(
882 const std::string
& item_id
,
883 sync_pb::AppListSpecifics::AppListItemType item_type
) {
884 DCHECK(!ContainsKey(sync_items_
, item_id
));
885 SyncItem
* sync_item
= new SyncItem(item_id
, item_type
);
886 sync_items_
[item_id
] = sync_item
;
890 void AppListSyncableService::DeleteSyncItemSpecifics(
891 const sync_pb::AppListSpecifics
& specifics
) {
892 const std::string
& item_id
= specifics
.item_id();
893 if (item_id
.empty()) {
894 LOG(ERROR
) << "Delete AppList item with empty ID";
897 VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id
.substr(0, 8);
898 SyncItemMap::iterator iter
= sync_items_
.find(item_id
);
899 if (iter
== sync_items_
.end())
901 sync_pb::AppListSpecifics::AppListItemType item_type
=
902 iter
->second
->item_type
;
903 VLOG(2) << this << " <- SYNC DELETE: " << iter
->second
->ToString();
905 sync_items_
.erase(iter
);
906 // Only delete apps from the model. Folders will be deleted when all
907 // children have been deleted.
908 if (item_type
== sync_pb::AppListSpecifics::TYPE_APP
) {
909 model_
->DeleteItem(item_id
);
910 } else if (item_type
== sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
911 if (IsDriveAppSyncId(item_id
) && drive_app_provider_
) {
912 drive_app_provider_
->RemoveUninstalledDriveAppFromSync(
913 GetDriveAppIdFromSyncId(item_id
));
918 std::string
AppListSyncableService::FindOrCreateOemFolder() {
919 AppListFolderItem
* oem_folder
= model_
->FindFolderItem(kOemFolderId
);
921 scoped_ptr
<AppListFolderItem
> new_folder(new AppListFolderItem(
922 kOemFolderId
, AppListFolderItem::FOLDER_TYPE_OEM
));
924 static_cast<AppListFolderItem
*>(model_
->AddItem(new_folder
.Pass()));
925 SyncItem
* oem_sync_item
= FindSyncItem(kOemFolderId
);
927 VLOG(1) << "Creating OEM folder from existing sync item: "
928 << oem_sync_item
->item_ordinal
.ToDebugString();
929 model_
->SetItemPosition(oem_folder
, oem_sync_item
->item_ordinal
);
931 model_
->SetItemPosition(oem_folder
, GetOemFolderPos());
932 // Do not create a sync item for the OEM folder here, do it in
933 // ResolveFolderPositions() when the item position is finalized.
936 model_
->SetItemName(oem_folder
, oem_folder_name_
);
937 return oem_folder
->id();
940 syncer::StringOrdinal
AppListSyncableService::GetOemFolderPos() {
941 VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_
;
942 if (!first_app_list_sync_
) {
943 VLOG(1) << "Sync items exist, placing OEM folder at end.";
944 syncer::StringOrdinal last
;
945 for (SyncItemMap::iterator iter
= sync_items_
.begin();
946 iter
!= sync_items_
.end(); ++iter
) {
947 SyncItem
* sync_item
= iter
->second
;
948 if (sync_item
->item_ordinal
.IsValid() &&
949 (!last
.IsValid() || sync_item
->item_ordinal
.GreaterThan(last
))) {
950 last
= sync_item
->item_ordinal
;
954 return last
.CreateAfter();
957 // Place the OEM folder just after the web store, which should always be
958 // followed by a pre-installed app (e.g. Search), so the poosition should be
959 // stable. TODO(stevenjb): consider explicitly setting the OEM folder location
960 // along with the name in ServicesCustomizationDocument::SetOemFolderName().
961 AppListItemList
* item_list
= model_
->top_level_item_list();
962 if (item_list
->item_count() == 0)
963 return syncer::StringOrdinal();
965 size_t oem_index
= 0;
966 for (; oem_index
< item_list
->item_count() - 1; ++oem_index
) {
967 AppListItem
* cur_item
= item_list
->item_at(oem_index
);
968 if (cur_item
->id() == extensions::kWebStoreAppId
)
971 syncer::StringOrdinal oem_ordinal
;
972 AppListItem
* prev
= item_list
->item_at(oem_index
);
973 if (oem_index
+ 1 < item_list
->item_count()) {
974 AppListItem
* next
= item_list
->item_at(oem_index
+ 1);
975 oem_ordinal
= prev
->position().CreateBetween(next
->position());
977 oem_ordinal
= prev
->position().CreateAfter();
979 VLOG(1) << "Placing OEM Folder at: " << oem_index
980 << " position: " << oem_ordinal
.ToDebugString();
984 bool AppListSyncableService::AppIsOem(const std::string
& id
) {
985 if (!extension_system_
->extension_service())
987 const extensions::Extension
* extension
=
988 extension_system_
->extension_service()->GetExtensionById(id
, true);
989 return extension
&& extension
->was_installed_by_oem();
992 std::string
AppListSyncableService::SyncItem::ToString() const {
993 std::string res
= item_id
.substr(0, 8);
994 if (item_type
== sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP
) {
995 res
+= " { RemoveDefault }";
997 res
+= " { " + item_name
+ " }";
998 res
+= " [" + item_ordinal
.ToDebugString() + "]";
999 if (!parent_id
.empty())
1000 res
+= " <" + parent_id
.substr(0, 8) + ">";
1005 } // namespace app_list