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 "chrome/browser/apps/drive/drive_app_provider.h"
9 #include "chrome/browser/chrome_notification_types.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_service.h"
13 #include "chrome/browser/ui/app_list/extension_app_item.h"
14 #include "chrome/browser/ui/app_list/extension_app_model_builder.h"
15 #include "chrome/browser/ui/host_desktop.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "content/public/browser/notification_source.h"
18 #include "extensions/browser/extension_prefs.h"
19 #include "extensions/browser/extension_system.h"
20 #include "grit/generated_resources.h"
21 #include "sync/api/sync_change_processor.h"
22 #include "sync/api/sync_data.h"
23 #include "sync/api/sync_merge_result.h"
24 #include "sync/protocol/sync.pb.h"
25 #include "ui/app_list/app_list_folder_item.h"
26 #include "ui/app_list/app_list_item.h"
27 #include "ui/app_list/app_list_model.h"
28 #include "ui/app_list/app_list_model_observer.h"
29 #include "ui/app_list/app_list_switches.h"
30 #include "ui/base/l10n/l10n_util.h"
32 using syncer::SyncChange;
38 const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263";
40 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics,
41 AppListSyncableService::SyncItem* item) {
42 DCHECK_EQ(item->item_id, specifics.item_id());
43 item->item_type = specifics.item_type();
44 item->item_name = specifics.item_name();
45 item->parent_id = specifics.parent_id();
46 if (!specifics.page_ordinal().empty())
47 item->page_ordinal = syncer::StringOrdinal(specifics.page_ordinal());
48 if (!specifics.item_ordinal().empty())
49 item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal());
52 bool UpdateSyncItemFromAppItem(const AppListItem* app_item,
53 AppListSyncableService::SyncItem* sync_item) {
54 DCHECK_EQ(sync_item->item_id, app_item->id());
56 if (app_list::switches::IsFolderUIEnabled() &&
57 sync_item->parent_id != app_item->folder_id()) {
58 sync_item->parent_id = app_item->folder_id();
61 if (sync_item->item_name != app_item->name()) {
62 sync_item->item_name = app_item->name();
65 if (!sync_item->item_ordinal.IsValid() ||
66 !app_item->position().Equals(sync_item->item_ordinal)) {
67 sync_item->item_ordinal = app_item->position();
70 // TODO(stevenjb): Set page_ordinal.
74 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
75 sync_pb::AppListSpecifics* specifics) {
77 specifics->set_item_id(item->item_id);
78 specifics->set_item_type(item->item_type);
79 specifics->set_item_name(item->item_name);
80 specifics->set_parent_id(item->parent_id);
81 if (item->page_ordinal.IsValid())
82 specifics->set_page_ordinal(item->page_ordinal.ToInternalValue());
83 if (item->item_ordinal.IsValid())
84 specifics->set_item_ordinal(item->item_ordinal.ToInternalValue());
87 syncer::SyncData GetSyncDataFromSyncItem(
88 const AppListSyncableService::SyncItem* item) {
89 sync_pb::EntitySpecifics specifics;
90 GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list());
91 return syncer::SyncData::CreateLocalData(item->item_id,
96 bool AppIsDefault(ExtensionService* service, const std::string& id) {
97 return service && extensions::ExtensionPrefs::Get(service->profile())
98 ->WasInstalledByDefault(id);
101 void UninstallExtension(ExtensionService* service, const std::string& id) {
102 if (service && service->GetInstalledExtension(id))
103 service->UninstallExtension(id, false, NULL);
106 bool GetAppListItemType(AppListItem* item,
107 sync_pb::AppListSpecifics::AppListItemType* type) {
108 const char* item_type = item->GetItemType();
109 if (item_type == ExtensionAppItem::kItemType) {
110 *type = sync_pb::AppListSpecifics::TYPE_APP;
111 } else if (item_type == AppListFolderItem::kItemType) {
112 *type = sync_pb::AppListSpecifics::TYPE_FOLDER;
114 LOG(ERROR) << "Unrecognized model type: " << item_type;
122 // AppListSyncableService::SyncItem
124 AppListSyncableService::SyncItem::SyncItem(
125 const std::string& id,
126 sync_pb::AppListSpecifics::AppListItemType type)
131 AppListSyncableService::SyncItem::~SyncItem() {
134 // AppListSyncableService::ModelObserver
136 class AppListSyncableService::ModelObserver : public AppListModelObserver {
138 explicit ModelObserver(AppListSyncableService* owner)
141 DVLOG(2) << owner_ << ": ModelObserver Added";
142 owner_->model()->AddObserver(this);
145 virtual ~ModelObserver() {
146 owner_->model()->RemoveObserver(this);
147 DVLOG(2) << owner_ << ": ModelObserver Removed";
151 // AppListModelObserver
152 virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE {
153 DCHECK(!adding_item_);
154 adding_item_ = item; // Ignore updates while adding an item.
155 VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString();
156 owner_->AddOrUpdateFromSyncItem(item);
160 virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE {
161 DCHECK(!adding_item_);
162 VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString();
163 // Don't sync folder removal in case the folder still exists on another
164 // device (e.g. with device specific items in it). Empty folders will be
165 // deleted when the last item is removed (in PruneEmptySyncFolders()).
166 if (item->GetItemType() == AppListFolderItem::kItemType)
168 owner_->RemoveSyncItem(item->id());
171 virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE {
173 // Adding an item may trigger update notifications which should be
175 DCHECK_EQ(adding_item_, item);
178 VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString();
179 owner_->UpdateSyncItem(item);
182 AppListSyncableService* owner_;
183 AppListItem* adding_item_; // Unowned pointer to item being added.
185 DISALLOW_COPY_AND_ASSIGN(ModelObserver);
188 // AppListSyncableService
190 AppListSyncableService::AppListSyncableService(
192 extensions::ExtensionSystem* extension_system)
194 extension_system_(extension_system),
195 model_(new AppListModel) {
196 if (!extension_system) {
197 LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem";
202 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
204 // Note: model_observer_ is constructed after the initial sync changes are
205 // received in MergeDataAndStartSyncing(). Changes to the model before that
206 // will be synced after the initial sync occurs.
207 if (extension_system->extension_service() &&
208 extension_system->extension_service()->is_ready()) {
213 // The extensions for this profile have not yet all been loaded.
214 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
215 content::Source<Profile>(profile));
218 AppListSyncableService::~AppListSyncableService() {
220 model_observer_.reset();
222 STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end());
225 void AppListSyncableService::BuildModel() {
226 // For now, use the AppListControllerDelegate associated with the native
227 // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller
228 // dependency and move the dependent methods from AppListControllerDelegate
229 // to an extension service delegate associated with this class.
230 AppListControllerDelegate* controller = NULL;
231 AppListService* service =
232 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE);
234 controller = service->GetControllerDelegate();
235 apps_builder_.reset(new ExtensionAppModelBuilder(controller));
237 if (app_list::switches::IsAppListSyncEnabled()) {
238 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
240 apps_builder_->InitializeWithService(this);
242 VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile.";
243 apps_builder_->InitializeWithProfile(profile_, model_.get());
246 if (app_list::switches::IsDriveAppsInAppListEnabled())
247 drive_app_provider_.reset(new DriveAppProvider(profile_));
250 void AppListSyncableService::Shutdown() {
251 // DriveAppProvider touches other KeyedServices in its dtor and needs be
252 // released in shutdown stage.
253 drive_app_provider_.reset();
256 void AppListSyncableService::Observe(
258 const content::NotificationSource& source,
259 const content::NotificationDetails& details) {
260 DCHECK_EQ(chrome::NOTIFICATION_EXTENSIONS_READY, type);
261 DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
262 registrar_.RemoveAll();
266 const AppListSyncableService::SyncItem*
267 AppListSyncableService::GetSyncItem(const std::string& id) const {
268 SyncItemMap::const_iterator iter = sync_items_.find(id);
269 if (iter != sync_items_.end())
274 void AppListSyncableService::SetOemFolderName(const std::string& name) {
275 oem_folder_name_ = name;
276 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
278 model_->SetItemName(oem_folder, oem_folder_name_);
281 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
282 SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
284 return; // Item is not valid.
286 std::string folder_id;
287 if (app_list::switches::IsFolderUIEnabled()) {
288 if (AppIsOem(app_item->id())) {
289 folder_id = FindOrCreateOemFolder();
290 VLOG(2) << this << ": AddItem to OEM folder: " << sync_item->ToString();
292 folder_id = sync_item->parent_id;
295 VLOG(2) << this << ": AddItem: " << sync_item->ToString()
296 << "Folder: '" << folder_id << "'";
297 model_->AddItemToFolder(app_item.Pass(), folder_id);
300 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem(
301 AppListItem* app_item) {
302 const std::string& item_id = app_item->id();
303 if (item_id.empty()) {
304 LOG(ERROR) << "AppListItem item with empty ID";
307 SyncItem* sync_item = FindSyncItem(item_id);
309 // If there is an existing, non-REMOVE_DEFAULT entry, return it.
310 if (sync_item->item_type !=
311 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
312 DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString();
316 if (RemoveDefaultApp(app_item, sync_item))
319 // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new
320 // App entry can be added.
323 return CreateSyncItemFromAppItem(app_item);
326 AppListSyncableService::SyncItem*
327 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) {
328 sync_pb::AppListSpecifics::AppListItemType type;
329 if (!GetAppListItemType(app_item, &type))
331 VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString();
332 SyncItem* sync_item = CreateSyncItem(app_item->id(), type);
333 UpdateSyncItemFromAppItem(app_item, sync_item);
334 SendSyncChange(sync_item, SyncChange::ACTION_ADD);
338 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) {
339 SyncItem* sync_item = FindSyncItem(app_item->id());
341 UpdateAppItemFromSyncItem(sync_item, app_item);
344 CreateSyncItemFromAppItem(app_item);
347 bool AppListSyncableService::RemoveDefaultApp(AppListItem* item,
348 SyncItem* sync_item) {
349 CHECK_EQ(sync_item->item_type,
350 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
352 // If there is an existing REMOVE_DEFAULT_APP entry, and the app is
353 // installed as a Default app, uninstall the app instead of adding it.
354 if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP &&
355 AppIsDefault(extension_system_->extension_service(), item->id())) {
356 VLOG(2) << this << ": HandleDefaultApp: Uninstall: "
357 << sync_item->ToString();
358 UninstallExtension(extension_system_->extension_service(), item->id());
362 // Otherwise, we are adding the app as a non-default app (i.e. an app that
363 // was installed by Default and removed is getting installed explicitly by
364 // the user), so delete the REMOVE_DEFAULT_APP.
365 DeleteSyncItem(sync_item);
369 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
371 VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString();
372 SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE,
373 GetSyncDataFromSyncItem(sync_item));
374 sync_processor_->ProcessSyncChanges(
375 FROM_HERE, syncer::SyncChangeList(1, sync_change));
377 std::string item_id = sync_item->item_id;
379 sync_items_.erase(item_id);
382 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
383 SyncItem* sync_item = FindSyncItem(app_item->id());
385 LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
388 bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
390 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
393 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
396 void AppListSyncableService::RemoveItem(const std::string& id) {
398 model_->DeleteItem(id);
399 PruneEmptySyncFolders();
402 void AppListSyncableService::UpdateItem(AppListItem* app_item) {
403 // Check to see if the item needs to be moved to/from the OEM folder.
404 if (!app_list::switches::IsFolderUIEnabled())
406 bool is_oem = AppIsOem(app_item->id());
407 if (!is_oem && app_item->folder_id() == kOemFolderId)
408 model_->MoveItemToFolder(app_item, "");
409 else if (is_oem && app_item->folder_id() != kOemFolderId)
410 model_->MoveItemToFolder(app_item, kOemFolderId);
413 void AppListSyncableService::RemoveSyncItem(const std::string& id) {
414 VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8);
415 SyncItemMap::iterator iter = sync_items_.find(id);
416 if (iter == sync_items_.end()) {
417 DVLOG(2) << this << " : RemoveSyncItem: No Item.";
421 // Check for existing RemoveDefault sync item.
422 SyncItem* sync_item = iter->second;
423 sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type;
424 if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
425 // RemoveDefault item exists, just return.
426 DVLOG(2) << this << " : RemoveDefault Item exists.";
430 if (type == sync_pb::AppListSpecifics::TYPE_APP &&
431 AppIsDefault(extension_system_->extension_service(), id)) {
432 // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This
433 // will overwrite any existing entry for the item.
434 VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: "
435 << sync_item->item_id;
436 sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP;
437 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
441 DeleteSyncItem(sync_item);
444 void AppListSyncableService::ResolveFolderPositions(bool move_oem_to_end) {
445 if (!app_list::switches::IsFolderUIEnabled())
448 for (SyncItemMap::iterator iter = sync_items_.begin();
449 iter != sync_items_.end(); ++iter) {
450 SyncItem* sync_item = iter->second;
451 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
453 AppListItem* app_item = model_->FindItem(sync_item->item_id);
456 if (move_oem_to_end && app_item->id() == kOemFolderId) {
457 // Move the OEM folder to the end.
458 model_->SetItemPosition(app_item, syncer::StringOrdinal());
460 UpdateAppItemFromSyncItem(sync_item, app_item);
464 void AppListSyncableService::PruneEmptySyncFolders() {
465 if (!app_list::switches::IsFolderUIEnabled())
468 std::set<std::string> parent_ids;
469 for (SyncItemMap::iterator iter = sync_items_.begin();
470 iter != sync_items_.end(); ++iter) {
471 parent_ids.insert(iter->second->parent_id);
473 for (SyncItemMap::iterator iter = sync_items_.begin();
474 iter != sync_items_.end(); ) {
475 SyncItem* sync_item = (iter++)->second;
476 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER)
478 if (!ContainsKey(parent_ids, sync_item->item_id))
479 DeleteSyncItem(sync_item);
483 // AppListSyncableService syncer::SyncableService
485 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing(
486 syncer::ModelType type,
487 const syncer::SyncDataList& initial_sync_data,
488 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
489 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
490 DCHECK(!sync_processor_.get());
491 DCHECK(sync_processor.get());
492 DCHECK(error_handler.get());
494 sync_processor_ = sync_processor.Pass();
495 sync_error_handler_ = error_handler.Pass();
496 if (switches::IsFolderUIEnabled())
497 model_->SetFoldersEnabled(true);
499 syncer::SyncMergeResult result = syncer::SyncMergeResult(type);
500 result.set_num_items_before_association(sync_items_.size());
501 VLOG(1) << this << ": MergeDataAndStartSyncing: "
502 << initial_sync_data.size();
504 // Copy all sync items to |unsynced_items|.
505 std::set<std::string> unsynced_items;
506 for (SyncItemMap::const_iterator iter = sync_items_.begin();
507 iter != sync_items_.end(); ++iter) {
508 unsynced_items.insert(iter->first);
511 // Create SyncItem entries for initial_sync_data.
512 size_t new_items = 0, updated_items = 0;
513 bool oem_folder_is_synced = false;
514 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
515 iter != initial_sync_data.end(); ++iter) {
516 const syncer::SyncData& data = *iter;
517 const std::string& item_id = data.GetSpecifics().app_list().item_id();
518 DVLOG(2) << this << " Initial Sync Item: " << item_id
519 << " Type: " << data.GetSpecifics().app_list().item_type();
520 DCHECK_EQ(syncer::APP_LIST, data.GetDataType());
521 if (ProcessSyncItemSpecifics(data.GetSpecifics().app_list()))
525 unsynced_items.erase(item_id);
526 if (item_id == kOemFolderId)
527 oem_folder_is_synced = true;
530 result.set_num_items_after_association(sync_items_.size());
531 result.set_num_items_added(new_items);
532 result.set_num_items_deleted(0);
533 result.set_num_items_modified(updated_items);
535 // Send unsynced items. Does not affect |result|.
536 syncer::SyncChangeList change_list;
537 for (std::set<std::string>::iterator iter = unsynced_items.begin();
538 iter != unsynced_items.end(); ++iter) {
539 SyncItem* sync_item = FindSyncItem(*iter);
540 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
541 change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD,
542 GetSyncDataFromSyncItem(sync_item)));
544 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
546 // Adding items may have created folders without setting their positions
547 // since we haven't started observing the item list yet. Resolve those.
548 // Also ensure the OEM folder is at the end if its position hasn't been set.
549 bool move_oem_to_end = !oem_folder_is_synced;
550 ResolveFolderPositions(move_oem_to_end);
552 // Start observing app list model changes.
553 model_observer_.reset(new ModelObserver(this));
558 void AppListSyncableService::StopSyncing(syncer::ModelType type) {
559 DCHECK_EQ(type, syncer::APP_LIST);
561 sync_processor_.reset();
562 sync_error_handler_.reset();
563 model_->SetFoldersEnabled(false);
566 syncer::SyncDataList AppListSyncableService::GetAllSyncData(
567 syncer::ModelType type) const {
568 DCHECK_EQ(syncer::APP_LIST, type);
570 VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size();
571 syncer::SyncDataList list;
572 for (SyncItemMap::const_iterator iter = sync_items_.begin();
573 iter != sync_items_.end(); ++iter) {
574 VLOG(2) << this << " -> SYNC: " << iter->second->ToString();
575 list.push_back(GetSyncDataFromSyncItem(iter->second));
580 syncer::SyncError AppListSyncableService::ProcessSyncChanges(
581 const tracked_objects::Location& from_here,
582 const syncer::SyncChangeList& change_list) {
583 if (!sync_processor_.get()) {
584 return syncer::SyncError(FROM_HERE,
585 syncer::SyncError::DATATYPE_ERROR,
586 "App List syncable service is not started.",
590 // Don't observe the model while processing incoming sync changes.
591 model_observer_.reset();
593 VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size();
594 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
595 iter != change_list.end(); ++iter) {
596 const SyncChange& change = *iter;
597 VLOG(2) << this << " Change: "
598 << change.sync_data().GetSpecifics().app_list().item_id()
599 << " (" << change.change_type() << ")";
600 if (change.change_type() == SyncChange::ACTION_ADD ||
601 change.change_type() == SyncChange::ACTION_UPDATE) {
602 ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
603 } else if (change.change_type() == SyncChange::ACTION_DELETE) {
604 DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list());
606 LOG(ERROR) << "Invalid sync change";
610 // Continue observing app list model changes.
611 model_observer_.reset(new ModelObserver(this));
613 return syncer::SyncError();
616 // AppListSyncableService private
618 bool AppListSyncableService::ProcessSyncItemSpecifics(
619 const sync_pb::AppListSpecifics& specifics) {
620 const std::string& item_id = specifics.item_id();
621 if (item_id.empty()) {
622 LOG(ERROR) << "AppList item with empty ID";
625 SyncItem* sync_item = FindSyncItem(item_id);
627 // If an item of the same type exists, update it.
628 if (sync_item->item_type == specifics.item_type()) {
629 UpdateSyncItemFromSync(specifics, sync_item);
630 ProcessExistingSyncItem(sync_item);
631 VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString();
634 // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP.
635 if (sync_item->item_type !=
636 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP &&
637 specifics.item_type() !=
638 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
639 LOG(ERROR) << "Synced item type: " << specifics.item_type()
640 << " != existing sync item type: " << sync_item->item_type
641 << " Deleting item from model!";
642 model_->DeleteItem(item_id);
644 VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: "
645 << sync_item->ToString();
647 sync_items_.erase(item_id);
650 sync_item = CreateSyncItem(item_id, specifics.item_type());
651 UpdateSyncItemFromSync(specifics, sync_item);
652 ProcessNewSyncItem(sync_item);
653 VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString();
657 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) {
658 VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString();
659 switch (sync_item->item_type) {
660 case sync_pb::AppListSpecifics::TYPE_APP: {
661 // New apps are added through ExtensionAppModelBuilder.
662 // TODO(stevenjb): Determine how to handle app items in sync that
663 // are not installed (e.g. default / OEM apps).
666 case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: {
667 VLOG(1) << this << ": Uninstall: " << sync_item->ToString();
668 UninstallExtension(extension_system_->extension_service(),
672 case sync_pb::AppListSpecifics::TYPE_FOLDER: {
673 AppListItem* app_item = model_->FindItem(sync_item->item_id);
675 return; // Don't create new folders here, the model will do that.
676 UpdateAppItemFromSyncItem(sync_item, app_item);
679 case sync_pb::AppListSpecifics::TYPE_URL: {
680 // TODO(stevenjb): Implement
681 LOG(WARNING) << "TYPE_URL not supported";
685 NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString();
688 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) {
689 if (sync_item->item_type ==
690 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
693 VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
694 AppListItem* app_item = model_->FindItem(sync_item->item_id);
695 DVLOG(2) << " AppItem: " << app_item->ToDebugString();
697 LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
700 // This is the only place where sync can cause an item to change folders.
701 if (app_list::switches::IsFolderUIEnabled() &&
702 app_item->folder_id() != sync_item->parent_id &&
703 !AppIsOem(app_item->id())) {
704 DVLOG(2) << " Moving Item To Folder: " << sync_item->parent_id;
705 model_->MoveItemToFolder(app_item, sync_item->parent_id);
707 UpdateAppItemFromSyncItem(sync_item, app_item);
710 void AppListSyncableService::UpdateAppItemFromSyncItem(
711 const AppListSyncableService::SyncItem* sync_item,
712 AppListItem* app_item) {
713 VLOG(2) << this << "UpdateAppItemFromSyncItem: " << sync_item->ToString();
714 if (!app_item->position().Equals(sync_item->item_ordinal))
715 model_->SetItemPosition(app_item, sync_item->item_ordinal);
716 // Only update the item name if it is a Folder or the name is empty.
717 if (sync_item->item_name != app_item->name() &&
718 sync_item->item_id != kOemFolderId &&
719 (app_item->GetItemType() == AppListFolderItem::kItemType ||
720 app_item->name().empty())) {
721 model_->SetItemName(app_item, sync_item->item_name);
725 bool AppListSyncableService::SyncStarted() {
726 if (sync_processor_.get())
728 if (flare_.is_null()) {
729 VLOG(1) << this << ": SyncStarted: Flare.";
730 flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath());
731 flare_.Run(syncer::APP_LIST);
736 void AppListSyncableService::SendSyncChange(
738 SyncChange::SyncChangeType sync_change_type) {
739 if (!SyncStarted()) {
740 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
741 << sync_item->ToString();
744 if (sync_change_type == SyncChange::ACTION_ADD)
745 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
747 VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString();
748 SyncChange sync_change(FROM_HERE, sync_change_type,
749 GetSyncDataFromSyncItem(sync_item));
750 sync_processor_->ProcessSyncChanges(
751 FROM_HERE, syncer::SyncChangeList(1, sync_change));
754 AppListSyncableService::SyncItem*
755 AppListSyncableService::FindSyncItem(const std::string& item_id) {
756 SyncItemMap::iterator iter = sync_items_.find(item_id);
757 if (iter == sync_items_.end())
762 AppListSyncableService::SyncItem*
763 AppListSyncableService::CreateSyncItem(
764 const std::string& item_id,
765 sync_pb::AppListSpecifics::AppListItemType item_type) {
766 DCHECK(!ContainsKey(sync_items_, item_id));
767 SyncItem* sync_item = new SyncItem(item_id, item_type);
768 sync_items_[item_id] = sync_item;
772 void AppListSyncableService::DeleteSyncItemSpecifics(
773 const sync_pb::AppListSpecifics& specifics) {
774 const std::string& item_id = specifics.item_id();
775 if (item_id.empty()) {
776 LOG(ERROR) << "Delete AppList item with empty ID";
779 VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8);
780 SyncItemMap::iterator iter = sync_items_.find(item_id);
781 if (iter == sync_items_.end())
783 sync_pb::AppListSpecifics::AppListItemType item_type =
784 iter->second->item_type;
785 VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
787 sync_items_.erase(iter);
788 // Only delete apps from the model. Folders will be deleted when all
789 // children have been deleted.
790 if (item_type == sync_pb::AppListSpecifics::TYPE_APP)
791 model_->DeleteItem(item_id);
794 std::string AppListSyncableService::FindOrCreateOemFolder() {
795 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
797 scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
798 kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
799 oem_folder = static_cast<AppListFolderItem*>(
800 model_->AddItem(new_folder.PassAs<app_list::AppListItem>()));
802 model_->SetItemName(oem_folder, oem_folder_name_);
803 return oem_folder->id();
806 bool AppListSyncableService::AppIsOem(const std::string& id) {
807 if (!extension_system_->extension_service())
809 const extensions::Extension* extension =
810 extension_system_->extension_service()->GetExtensionById(id, true);
811 return extension && extension->was_installed_by_oem();
814 std::string AppListSyncableService::SyncItem::ToString() const {
815 std::string res = item_id.substr(0, 8);
816 if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) {
817 res += " { RemoveDefault }";
819 res += " { " + item_name + " }";
820 res += " [" + item_ordinal.ToDebugString() + "]";
821 if (!parent_id.empty())
822 res += " <" + parent_id.substr(0, 8) + ">";
827 } // namespace app_list