[Password manager] Temporarily add some CHECKs to investigate a crash
[chromium-blink-merge.git] / chrome / browser / ui / app_list / app_list_syncable_service.cc
blob0932d2cfa7a82b8ae2a448c170f8597bc6515760
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"
39 #endif
41 using syncer::SyncChange;
43 namespace app_list {
45 namespace {
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());
69 bool changed = false;
70 if (app_list::switches::IsFolderUIEnabled() &&
71 sync_item->parent_id != app_item->folder_id()) {
72 sync_item->parent_id = app_item->folder_id();
73 changed = true;
75 if (sync_item->item_name != app_item->name()) {
76 sync_item->item_name = app_item->name();
77 changed = true;
79 if (!sync_item->item_ordinal.IsValid() ||
80 !app_item->position().Equals(sync_item->item_ordinal)) {
81 sync_item->item_ordinal = app_item->position();
82 changed = true;
84 return changed;
87 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item,
88 sync_pb::AppListSpecifics* specifics) {
89 DCHECK(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,
103 item->item_id,
104 specifics);
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)
115 return true;
116 #if defined(OS_CHROMEOS)
117 if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId)
118 return true;
119 #endif
120 return false;
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),
128 NULL);
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;
139 } else {
140 LOG(ERROR) << "Unrecognized model type: " << item_type;
141 return false;
143 return true;
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));
160 } // namespace
162 // AppListSyncableService::SyncItem
164 AppListSyncableService::SyncItem::SyncItem(
165 const std::string& id,
166 sync_pb::AppListSpecifics::AppListItemType type)
167 : item_id(id),
168 item_type(type) {
171 AppListSyncableService::SyncItem::~SyncItem() {
174 // AppListSyncableService::ModelObserver
176 class AppListSyncableService::ModelObserver : public AppListModelObserver {
177 public:
178 explicit ModelObserver(AppListSyncableService* owner)
179 : owner_(owner),
180 adding_item_(NULL) {
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";
190 private:
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);
197 adding_item_ = NULL;
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)
207 return;
208 owner_->RemoveSyncItem(item->id());
211 void OnAppListItemUpdated(AppListItem* item) override {
212 if (adding_item_) {
213 // Adding an item may trigger update notifications which should be
214 // ignored.
215 DCHECK_EQ(adding_item_, item);
216 return;
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(
231 Profile* profile,
232 extensions::ExtensionSystem* extension_system)
233 : profile_(profile),
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";
240 return;
243 oem_folder_name_ =
244 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME);
247 AppListSyncableService::~AppListSyncableService() {
248 // Remove observers.
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);
266 if (service)
267 controller = service->GetControllerDelegate();
268 apps_builder_.reset(new ExtensionAppModelBuilder(controller));
269 DCHECK(profile_);
270 if (app_list::switches::IsAppListSyncEnabled()) {
271 VLOG(1) << this << ": AppListSyncableService: InitializeWithService.";
272 SyncStarted();
273 apps_builder_->InitializeWithService(this, model_.get());
274 } else {
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.
288 GetModel();
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);
307 if (sync_item) {
308 DCHECK_EQ(sync_item->item_type,
309 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP);
310 return;
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);
322 if (!sync_item)
323 return;
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())
334 return iter->second;
335 return NULL;
338 void AppListSyncableService::SetOemFolderName(const std::string& name) {
339 oem_folder_name_ = name;
340 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId);
341 if (oem_folder)
342 model_->SetItemName(oem_folder, oem_folder_name_);
345 AppListModel* AppListSyncableService::GetModel() {
346 if (!apps_builder_)
347 BuildModel();
349 return model_.get();
352 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) {
353 SyncItem* sync_item = FindOrAddSyncItem(app_item.get());
354 if (!sync_item)
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();
363 } else {
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";
377 return NULL;
379 SyncItem* sync_item = FindSyncItem(item_id);
380 if (sync_item) {
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();
385 return sync_item;
388 if (RemoveDefaultApp(app_item, sync_item))
389 return NULL;
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))
402 return NULL;
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);
407 return sync_item;
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)
414 return;
416 SyncItem* sync_item = FindSyncItem(app_item->id());
417 if (sync_item) {
418 UpdateAppItemFromSyncItem(sync_item, app_item);
419 return;
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());
436 return true;
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);
443 return false;
446 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) {
447 if (SyncStarted()) {
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;
455 delete sync_item;
456 sync_items_.erase(item_id);
459 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) {
460 SyncItem* sync_item = FindSyncItem(app_item->id());
461 if (!sync_item) {
462 LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id();
463 return;
465 bool changed = UpdateSyncItemFromAppItem(app_item, sync_item);
466 if (!changed) {
467 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString();
468 return;
470 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE);
473 void AppListSyncableService::RemoveItem(const std::string& id) {
474 RemoveSyncItem(id);
475 model_->DeleteItem(id);
476 PruneEmptySyncFolders();
479 void AppListSyncableService::RemoveUninstalledItem(const std::string& id) {
480 RemoveSyncItem(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())
488 return;
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.";
501 return;
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.";
510 return;
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);
521 return;
524 DeleteSyncItem(sync_item);
527 void AppListSyncableService::ResolveFolderPositions() {
528 if (!app_list::switches::IsFolderUIEnabled())
529 return;
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)
536 continue;
537 AppListItem* app_item = model_->FindItem(sync_item->item_id);
538 if (!app_item)
539 continue;
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())
555 return;
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)
566 continue;
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.
584 GetModel();
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))
614 ++new_items;
615 else
616 ++updated_items;
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.
641 if (!sync_item)
642 continue;
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));
656 return result;
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));
678 return list;
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.",
688 syncer::APP_LIST);
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());
706 } else {
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";
724 return false;
726 SyncItem* sync_item = FindSyncItem(item_id);
727 if (sync_item) {
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();
733 return false;
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();
747 delete sync_item;
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();
755 return true;
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).
765 return;
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));
774 } else {
775 UninstallExtension(extension_system_->extension_service(),
776 sync_item->item_id);
778 return;
780 case sync_pb::AppListSpecifics::TYPE_FOLDER: {
781 AppListItem* app_item = model_->FindItem(sync_item->item_id);
782 if (!app_item)
783 return; // Don't create new folders here, the model will do that.
784 UpdateAppItemFromSyncItem(sync_item, app_item);
785 return;
787 case sync_pb::AppListSpecifics::TYPE_URL: {
788 // TODO(stevenjb): Implement
789 LOG(WARNING) << "TYPE_URL not supported";
790 return;
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) {
799 return;
801 VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString();
802 AppListItem* app_item = model_->FindItem(sync_item->item_id);
803 DVLOG(2) << " AppItem: " << app_item->ToDebugString();
804 if (!app_item) {
805 LOG(ERROR) << "Item not found in model: " << sync_item->ToString();
806 return;
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())
835 return true;
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);
841 return false;
844 void AppListSyncableService::SendSyncChange(
845 SyncItem* sync_item,
846 SyncChange::SyncChangeType sync_change_type) {
847 if (!SyncStarted()) {
848 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: "
849 << sync_item->ToString();
850 return;
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();
860 return;
862 if (sync_change_type == SyncChange::ACTION_ADD)
863 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString();
864 else
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())
876 return NULL;
877 return iter->second;
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;
887 return 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";
895 return;
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())
900 return;
901 sync_pb::AppListSpecifics::AppListItemType item_type =
902 iter->second->item_type;
903 VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString();
904 delete iter->second;
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);
920 if (!oem_folder) {
921 scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem(
922 kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM));
923 oem_folder =
924 static_cast<AppListFolderItem*>(model_->AddItem(new_folder.Pass()));
925 SyncItem* oem_sync_item = FindSyncItem(kOemFolderId);
926 if (oem_sync_item) {
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);
930 } else {
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;
953 if (last.IsValid())
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)
969 break;
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());
976 } else {
977 oem_ordinal = prev->position().CreateAfter();
979 VLOG(1) << "Placing OEM Folder at: " << oem_index
980 << " position: " << oem_ordinal.ToDebugString();
981 return oem_ordinal;
984 bool AppListSyncableService::AppIsOem(const std::string& id) {
985 if (!extension_system_->extension_service())
986 return false;
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 }";
996 } else {
997 res += " { " + item_name + " }";
998 res += " [" + item_ordinal.ToDebugString() + "]";
999 if (!parent_id.empty())
1000 res += " <" + parent_id.substr(0, 8) + ">";
1002 return res;
1005 } // namespace app_list