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. 4 5 #include "chrome/browser/ui/app_list/app_list_syncable_service.h" 6 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 "chrome/common/extensions/extension_constants.h" 18 #include "chrome/grit/generated_resources.h" 19 #include "content/public/browser/notification_source.h" 20 #include "extensions/browser/extension_prefs.h" 21 #include "extensions/browser/extension_system.h" 22 #include "extensions/browser/uninstall_reason.h" 23 #include "extensions/common/constants.h" 24 #include "sync/api/sync_change_processor.h" 25 #include "sync/api/sync_data.h" 26 #include "sync/api/sync_merge_result.h" 27 #include "sync/protocol/sync.pb.h" 28 #include "ui/app_list/app_list_folder_item.h" 29 #include "ui/app_list/app_list_item.h" 30 #include "ui/app_list/app_list_model.h" 31 #include "ui/app_list/app_list_model_observer.h" 32 #include "ui/app_list/app_list_switches.h" 33 #include "ui/base/l10n/l10n_util.h" 34 35 #if defined(OS_CHROMEOS) 36 #include "chrome/browser/chromeos/file_manager/app_id.h" 37 #include "chrome/browser/chromeos/genius_app/app_id.h" 38 #endif 39 40 using syncer::SyncChange; 41 42 namespace app_list { 43 44 namespace { 45 46 const char kOemFolderId[] = "ddb1da55-d478-4243-8642-56d3041f0263"; 47 48 void UpdateSyncItemFromSync(const sync_pb::AppListSpecifics& specifics, 49 AppListSyncableService::SyncItem* item) { 50 DCHECK_EQ(item->item_id, specifics.item_id()); 51 item->item_type = specifics.item_type(); 52 item->item_name = specifics.item_name(); 53 item->parent_id = specifics.parent_id(); 54 if (!specifics.page_ordinal().empty()) 55 item->page_ordinal = syncer::StringOrdinal(specifics.page_ordinal()); 56 if (!specifics.item_ordinal().empty()) 57 item->item_ordinal = syncer::StringOrdinal(specifics.item_ordinal()); 58 } 59 60 bool UpdateSyncItemFromAppItem(const AppListItem* app_item, 61 AppListSyncableService::SyncItem* sync_item) { 62 DCHECK_EQ(sync_item->item_id, app_item->id()); 63 bool changed = false; 64 if (app_list::switches::IsFolderUIEnabled() && 65 sync_item->parent_id != app_item->folder_id()) { 66 sync_item->parent_id = app_item->folder_id(); 67 changed = true; 68 } 69 if (sync_item->item_name != app_item->name()) { 70 sync_item->item_name = app_item->name(); 71 changed = true; 72 } 73 if (!sync_item->item_ordinal.IsValid() || 74 !app_item->position().Equals(sync_item->item_ordinal)) { 75 sync_item->item_ordinal = app_item->position(); 76 changed = true; 77 } 78 // TODO(stevenjb): Set page_ordinal. 79 return changed; 80 } 81 82 void GetSyncSpecificsFromSyncItem(const AppListSyncableService::SyncItem* item, 83 sync_pb::AppListSpecifics* specifics) { 84 DCHECK(specifics); 85 specifics->set_item_id(item->item_id); 86 specifics->set_item_type(item->item_type); 87 specifics->set_item_name(item->item_name); 88 specifics->set_parent_id(item->parent_id); 89 if (item->page_ordinal.IsValid()) 90 specifics->set_page_ordinal(item->page_ordinal.ToInternalValue()); 91 if (item->item_ordinal.IsValid()) 92 specifics->set_item_ordinal(item->item_ordinal.ToInternalValue()); 93 } 94 95 syncer::SyncData GetSyncDataFromSyncItem( 96 const AppListSyncableService::SyncItem* item) { 97 sync_pb::EntitySpecifics specifics; 98 GetSyncSpecificsFromSyncItem(item, specifics.mutable_app_list()); 99 return syncer::SyncData::CreateLocalData(item->item_id, 100 item->item_id, 101 specifics); 102 } 103 104 bool AppIsDefault(ExtensionService* service, const std::string& id) { 105 return service && extensions::ExtensionPrefs::Get(service->profile()) 106 ->WasInstalledByDefault(id); 107 } 108 109 bool IsUnRemovableDefaultApp(const std::string& id) { 110 if (id == extension_misc::kChromeAppId || 111 id == extensions::kWebStoreAppId) 112 return true; 113 #if defined(OS_CHROMEOS) 114 if (id == file_manager::kFileManagerAppId || id == genius_app::kGeniusAppId) 115 return true; 116 #endif 117 return false; 118 } 119 120 void UninstallExtension(ExtensionService* service, const std::string& id) { 121 if (service && service->GetInstalledExtension(id)) { 122 service->UninstallExtension(id, 123 extensions::UNINSTALL_REASON_SYNC, 124 base::Bind(&base::DoNothing), 125 NULL); 126 } 127 } 128 129 bool GetAppListItemType(AppListItem* item, 130 sync_pb::AppListSpecifics::AppListItemType* type) { 131 const char* item_type = item->GetItemType(); 132 if (item_type == ExtensionAppItem::kItemType) { 133 *type = sync_pb::AppListSpecifics::TYPE_APP; 134 } else if (item_type == AppListFolderItem::kItemType) { 135 *type = sync_pb::AppListSpecifics::TYPE_FOLDER; 136 } else { 137 LOG(ERROR) << "Unrecognized model type: " << item_type; 138 return false; 139 } 140 return true; 141 } 142 143 } // namespace 144 145 // AppListSyncableService::SyncItem 146 147 AppListSyncableService::SyncItem::SyncItem( 148 const std::string& id, 149 sync_pb::AppListSpecifics::AppListItemType type) 150 : item_id(id), 151 item_type(type) { 152 } 153 154 AppListSyncableService::SyncItem::~SyncItem() { 155 } 156 157 // AppListSyncableService::ModelObserver 158 159 class AppListSyncableService::ModelObserver : public AppListModelObserver { 160 public: 161 explicit ModelObserver(AppListSyncableService* owner) 162 : owner_(owner), 163 adding_item_(NULL) { 164 DVLOG(2) << owner_ << ": ModelObserver Added"; 165 owner_->model()->AddObserver(this); 166 } 167 168 virtual ~ModelObserver() { 169 owner_->model()->RemoveObserver(this); 170 DVLOG(2) << owner_ << ": ModelObserver Removed"; 171 } 172 173 private: 174 // AppListModelObserver 175 virtual void OnAppListItemAdded(AppListItem* item) OVERRIDE { 176 DCHECK(!adding_item_); 177 adding_item_ = item; // Ignore updates while adding an item. 178 VLOG(2) << owner_ << " OnAppListItemAdded: " << item->ToDebugString(); 179 owner_->AddOrUpdateFromSyncItem(item); 180 adding_item_ = NULL; 181 } 182 183 virtual void OnAppListItemWillBeDeleted(AppListItem* item) OVERRIDE { 184 DCHECK(!adding_item_); 185 VLOG(2) << owner_ << " OnAppListItemDeleted: " << item->ToDebugString(); 186 // Don't sync folder removal in case the folder still exists on another 187 // device (e.g. with device specific items in it). Empty folders will be 188 // deleted when the last item is removed (in PruneEmptySyncFolders()). 189 if (item->GetItemType() == AppListFolderItem::kItemType) 190 return; 191 owner_->RemoveSyncItem(item->id()); 192 } 193 194 virtual void OnAppListItemUpdated(AppListItem* item) OVERRIDE { 195 if (adding_item_) { 196 // Adding an item may trigger update notifications which should be 197 // ignored. 198 DCHECK_EQ(adding_item_, item); 199 return; 200 } 201 VLOG(2) << owner_ << " OnAppListItemUpdated: " << item->ToDebugString(); 202 owner_->UpdateSyncItem(item); 203 } 204 205 AppListSyncableService* owner_; 206 AppListItem* adding_item_; // Unowned pointer to item being added. 207 208 DISALLOW_COPY_AND_ASSIGN(ModelObserver); 209 }; 210 211 // AppListSyncableService 212 213 AppListSyncableService::AppListSyncableService( 214 Profile* profile, 215 extensions::ExtensionSystem* extension_system) 216 : profile_(profile), 217 extension_system_(extension_system), 218 model_(new AppListModel), 219 initial_sync_data_processed_(false), 220 first_app_list_sync_(true) { 221 if (!extension_system) { 222 LOG(ERROR) << "AppListSyncableService created with no ExtensionSystem"; 223 return; 224 } 225 226 oem_folder_name_ = 227 l10n_util::GetStringUTF8(IDS_APP_LIST_OEM_DEFAULT_FOLDER_NAME); 228 229 // Note: model_observer_ is constructed after the initial sync changes are 230 // received in MergeDataAndStartSyncing(). Changes to the model before that 231 // will be synced after the initial sync occurs. 232 if (extension_system->extension_service() && 233 extension_system->extension_service()->is_ready()) { 234 BuildModel(); 235 return; 236 } 237 238 // The extensions for this profile have not yet all been loaded. 239 registrar_.Add(this, 240 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, 241 content::Source<Profile>(profile)); 242 } 243 244 AppListSyncableService::~AppListSyncableService() { 245 // Remove observers. 246 model_observer_.reset(); 247 248 STLDeleteContainerPairSecondPointers(sync_items_.begin(), sync_items_.end()); 249 } 250 251 void AppListSyncableService::BuildModel() { 252 // For now, use the AppListControllerDelegate associated with the native 253 // desktop. TODO(stevenjb): Remove ExtensionAppModelBuilder controller 254 // dependency and move the dependent methods from AppListControllerDelegate 255 // to an extension service delegate associated with this class. 256 AppListControllerDelegate* controller = NULL; 257 AppListService* service = 258 AppListService::Get(chrome::HOST_DESKTOP_TYPE_NATIVE); 259 if (service) 260 controller = service->GetControllerDelegate(); 261 apps_builder_.reset(new ExtensionAppModelBuilder(controller)); 262 DCHECK(profile_); 263 if (app_list::switches::IsAppListSyncEnabled()) { 264 VLOG(1) << this << ": AppListSyncableService: InitializeWithService."; 265 SyncStarted(); 266 apps_builder_->InitializeWithService(this); 267 } else { 268 VLOG(1) << this << ": AppListSyncableService: InitializeWithProfile."; 269 apps_builder_->InitializeWithProfile(profile_, model_.get()); 270 } 271 272 if (app_list::switches::IsDriveAppsInAppListEnabled()) 273 drive_app_provider_.reset(new DriveAppProvider(profile_)); 274 } 275 276 void AppListSyncableService::ResetDriveAppProviderForTest() { 277 drive_app_provider_.reset(); 278 } 279 280 void AppListSyncableService::Shutdown() { 281 // DriveAppProvider touches other KeyedServices in its dtor and needs be 282 // released in shutdown stage. 283 drive_app_provider_.reset(); 284 } 285 286 void AppListSyncableService::Observe( 287 int type, 288 const content::NotificationSource& source, 289 const content::NotificationDetails& details) { 290 DCHECK_EQ(extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, type); 291 DCHECK_EQ(profile_, content::Source<Profile>(source).ptr()); 292 registrar_.RemoveAll(); 293 BuildModel(); 294 } 295 296 const AppListSyncableService::SyncItem* 297 AppListSyncableService::GetSyncItem(const std::string& id) const { 298 SyncItemMap::const_iterator iter = sync_items_.find(id); 299 if (iter != sync_items_.end()) 300 return iter->second; 301 return NULL; 302 } 303 304 void AppListSyncableService::SetOemFolderName(const std::string& name) { 305 oem_folder_name_ = name; 306 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId); 307 if (oem_folder) 308 model_->SetItemName(oem_folder, oem_folder_name_); 309 } 310 311 void AppListSyncableService::AddItem(scoped_ptr<AppListItem> app_item) { 312 SyncItem* sync_item = FindOrAddSyncItem(app_item.get()); 313 if (!sync_item) 314 return; // Item is not valid. 315 316 std::string folder_id; 317 if (app_list::switches::IsFolderUIEnabled()) { 318 if (AppIsOem(app_item->id())) { 319 folder_id = FindOrCreateOemFolder(); 320 VLOG_IF(2, !folder_id.empty()) 321 << this << ": AddItem to OEM folder: " << sync_item->ToString(); 322 } else { 323 folder_id = sync_item->parent_id; 324 } 325 } 326 VLOG(2) << this << ": AddItem: " << sync_item->ToString() 327 << " Folder: '" << folder_id << "'"; 328 model_->AddItemToFolder(app_item.Pass(), folder_id); 329 } 330 331 AppListSyncableService::SyncItem* AppListSyncableService::FindOrAddSyncItem( 332 AppListItem* app_item) { 333 const std::string& item_id = app_item->id(); 334 if (item_id.empty()) { 335 LOG(ERROR) << "AppListItem item with empty ID"; 336 return NULL; 337 } 338 SyncItem* sync_item = FindSyncItem(item_id); 339 if (sync_item) { 340 // If there is an existing, non-REMOVE_DEFAULT entry, return it. 341 if (sync_item->item_type != 342 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) { 343 DVLOG(2) << this << ": AddItem already exists: " << sync_item->ToString(); 344 return sync_item; 345 } 346 347 if (RemoveDefaultApp(app_item, sync_item)) 348 return NULL; 349 350 // Fall through. The REMOVE_DEFAULT_APP entry has been deleted, now a new 351 // App entry can be added. 352 } 353 354 return CreateSyncItemFromAppItem(app_item); 355 } 356 357 AppListSyncableService::SyncItem* 358 AppListSyncableService::CreateSyncItemFromAppItem(AppListItem* app_item) { 359 sync_pb::AppListSpecifics::AppListItemType type; 360 if (!GetAppListItemType(app_item, &type)) 361 return NULL; 362 VLOG(2) << this << " CreateSyncItemFromAppItem:" << app_item->ToDebugString(); 363 SyncItem* sync_item = CreateSyncItem(app_item->id(), type); 364 UpdateSyncItemFromAppItem(app_item, sync_item); 365 SendSyncChange(sync_item, SyncChange::ACTION_ADD); 366 return sync_item; 367 } 368 369 void AppListSyncableService::AddOrUpdateFromSyncItem(AppListItem* app_item) { 370 // Do not create a sync item for the OEM folder here, do that in 371 // ResolveFolderPositions once the position has been resolved. 372 if (app_item->id() == kOemFolderId) 373 return; 374 375 SyncItem* sync_item = FindSyncItem(app_item->id()); 376 if (sync_item) { 377 UpdateAppItemFromSyncItem(sync_item, app_item); 378 return; 379 } 380 CreateSyncItemFromAppItem(app_item); 381 } 382 383 bool AppListSyncableService::RemoveDefaultApp(AppListItem* item, 384 SyncItem* sync_item) { 385 CHECK_EQ(sync_item->item_type, 386 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP); 387 388 // If there is an existing REMOVE_DEFAULT_APP entry, and the app is 389 // installed as a Default app, uninstall the app instead of adding it. 390 if (sync_item->item_type == sync_pb::AppListSpecifics::TYPE_APP && 391 AppIsDefault(extension_system_->extension_service(), item->id())) { 392 VLOG(2) << this << ": HandleDefaultApp: Uninstall: " 393 << sync_item->ToString(); 394 UninstallExtension(extension_system_->extension_service(), item->id()); 395 return true; 396 } 397 398 // Otherwise, we are adding the app as a non-default app (i.e. an app that 399 // was installed by Default and removed is getting installed explicitly by 400 // the user), so delete the REMOVE_DEFAULT_APP. 401 DeleteSyncItem(sync_item); 402 return false; 403 } 404 405 void AppListSyncableService::DeleteSyncItem(SyncItem* sync_item) { 406 if (SyncStarted()) { 407 VLOG(2) << this << " -> SYNC DELETE: " << sync_item->ToString(); 408 SyncChange sync_change(FROM_HERE, SyncChange::ACTION_DELETE, 409 GetSyncDataFromSyncItem(sync_item)); 410 sync_processor_->ProcessSyncChanges( 411 FROM_HERE, syncer::SyncChangeList(1, sync_change)); 412 } 413 std::string item_id = sync_item->item_id; 414 delete sync_item; 415 sync_items_.erase(item_id); 416 } 417 418 void AppListSyncableService::UpdateSyncItem(AppListItem* app_item) { 419 SyncItem* sync_item = FindSyncItem(app_item->id()); 420 if (!sync_item) { 421 LOG(ERROR) << "UpdateItem: no sync item: " << app_item->id(); 422 return; 423 } 424 bool changed = UpdateSyncItemFromAppItem(app_item, sync_item); 425 if (!changed) { 426 DVLOG(2) << this << " - Update: SYNC NO CHANGE: " << sync_item->ToString(); 427 return; 428 } 429 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE); 430 } 431 432 void AppListSyncableService::RemoveItem(const std::string& id) { 433 RemoveSyncItem(id); 434 model_->DeleteItem(id); 435 PruneEmptySyncFolders(); 436 } 437 438 void AppListSyncableService::UpdateItem(AppListItem* app_item) { 439 // Check to see if the item needs to be moved to/from the OEM folder. 440 if (!app_list::switches::IsFolderUIEnabled()) 441 return; 442 bool is_oem = AppIsOem(app_item->id()); 443 if (!is_oem && app_item->folder_id() == kOemFolderId) 444 model_->MoveItemToFolder(app_item, ""); 445 else if (is_oem && app_item->folder_id() != kOemFolderId) 446 model_->MoveItemToFolder(app_item, kOemFolderId); 447 } 448 449 void AppListSyncableService::RemoveSyncItem(const std::string& id) { 450 VLOG(2) << this << ": RemoveSyncItem: " << id.substr(0, 8); 451 SyncItemMap::iterator iter = sync_items_.find(id); 452 if (iter == sync_items_.end()) { 453 DVLOG(2) << this << " : RemoveSyncItem: No Item."; 454 return; 455 } 456 457 // Check for existing RemoveDefault sync item. 458 SyncItem* sync_item = iter->second; 459 sync_pb::AppListSpecifics::AppListItemType type = sync_item->item_type; 460 if (type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) { 461 // RemoveDefault item exists, just return. 462 DVLOG(2) << this << " : RemoveDefault Item exists."; 463 return; 464 } 465 466 if (type == sync_pb::AppListSpecifics::TYPE_APP && 467 AppIsDefault(extension_system_->extension_service(), id)) { 468 // This is a Default app; update the entry to a REMOVE_DEFAULT entry. This 469 // will overwrite any existing entry for the item. 470 VLOG(2) << this << " -> SYNC UPDATE: REMOVE_DEFAULT: " 471 << sync_item->item_id; 472 sync_item->item_type = sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP; 473 SendSyncChange(sync_item, SyncChange::ACTION_UPDATE); 474 return; 475 } 476 477 DeleteSyncItem(sync_item); 478 } 479 480 void AppListSyncableService::ResolveFolderPositions() { 481 if (!app_list::switches::IsFolderUIEnabled()) 482 return; 483 484 VLOG(1) << "ResolveFolderPositions."; 485 for (SyncItemMap::iterator iter = sync_items_.begin(); 486 iter != sync_items_.end(); ++iter) { 487 SyncItem* sync_item = iter->second; 488 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER) 489 continue; 490 AppListItem* app_item = model_->FindItem(sync_item->item_id); 491 if (!app_item) 492 continue; 493 UpdateAppItemFromSyncItem(sync_item, app_item); 494 } 495 496 // Move the OEM folder if one exists and we have not synced its position. 497 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId); 498 if (oem_folder && !FindSyncItem(kOemFolderId)) { 499 model_->SetItemPosition(oem_folder, GetOemFolderPos()); 500 VLOG(1) << "Creating new OEM folder sync item: " 501 << oem_folder->position().ToDebugString(); 502 CreateSyncItemFromAppItem(oem_folder); 503 } 504 } 505 506 void AppListSyncableService::PruneEmptySyncFolders() { 507 if (!app_list::switches::IsFolderUIEnabled()) 508 return; 509 510 std::set<std::string> parent_ids; 511 for (SyncItemMap::iterator iter = sync_items_.begin(); 512 iter != sync_items_.end(); ++iter) { 513 parent_ids.insert(iter->second->parent_id); 514 } 515 for (SyncItemMap::iterator iter = sync_items_.begin(); 516 iter != sync_items_.end(); ) { 517 SyncItem* sync_item = (iter++)->second; 518 if (sync_item->item_type != sync_pb::AppListSpecifics::TYPE_FOLDER) 519 continue; 520 if (!ContainsKey(parent_ids, sync_item->item_id)) 521 DeleteSyncItem(sync_item); 522 } 523 } 524 525 // AppListSyncableService syncer::SyncableService 526 527 syncer::SyncMergeResult AppListSyncableService::MergeDataAndStartSyncing( 528 syncer::ModelType type, 529 const syncer::SyncDataList& initial_sync_data, 530 scoped_ptr<syncer::SyncChangeProcessor> sync_processor, 531 scoped_ptr<syncer::SyncErrorFactory> error_handler) { 532 DCHECK(!sync_processor_.get()); 533 DCHECK(sync_processor.get()); 534 DCHECK(error_handler.get()); 535 536 sync_processor_ = sync_processor.Pass(); 537 sync_error_handler_ = error_handler.Pass(); 538 if (switches::IsFolderUIEnabled()) 539 model_->SetFoldersEnabled(true); 540 541 syncer::SyncMergeResult result = syncer::SyncMergeResult(type); 542 result.set_num_items_before_association(sync_items_.size()); 543 VLOG(1) << this << ": MergeDataAndStartSyncing: " 544 << initial_sync_data.size(); 545 546 // Copy all sync items to |unsynced_items|. 547 std::set<std::string> unsynced_items; 548 for (SyncItemMap::const_iterator iter = sync_items_.begin(); 549 iter != sync_items_.end(); ++iter) { 550 unsynced_items.insert(iter->first); 551 } 552 553 // Create SyncItem entries for initial_sync_data. 554 size_t new_items = 0, updated_items = 0; 555 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin(); 556 iter != initial_sync_data.end(); ++iter) { 557 const syncer::SyncData& data = *iter; 558 const std::string& item_id = data.GetSpecifics().app_list().item_id(); 559 const sync_pb::AppListSpecifics& specifics = data.GetSpecifics().app_list(); 560 DVLOG(2) << this << " Initial Sync Item: " << item_id 561 << " Type: " << specifics.item_type(); 562 DCHECK_EQ(syncer::APP_LIST, data.GetDataType()); 563 if (ProcessSyncItemSpecifics(specifics)) 564 ++new_items; 565 else 566 ++updated_items; 567 if (specifics.item_type() != sync_pb::AppListSpecifics::TYPE_FOLDER && 568 !IsUnRemovableDefaultApp(item_id) && 569 !AppIsOem(item_id) && 570 !AppIsDefault(extension_system_->extension_service(), item_id)) { 571 VLOG(2) << "Syncing non-default item: " << item_id; 572 first_app_list_sync_ = false; 573 } 574 unsynced_items.erase(item_id); 575 } 576 result.set_num_items_after_association(sync_items_.size()); 577 result.set_num_items_added(new_items); 578 result.set_num_items_deleted(0); 579 result.set_num_items_modified(updated_items); 580 581 // Initial sync data has been processed, it is safe now to add new sync items. 582 initial_sync_data_processed_ = true; 583 584 // Send unsynced items. Does not affect |result|. 585 syncer::SyncChangeList change_list; 586 for (std::set<std::string>::iterator iter = unsynced_items.begin(); 587 iter != unsynced_items.end(); ++iter) { 588 SyncItem* sync_item = FindSyncItem(*iter); 589 // Sync can cause an item to change folders, causing an unsynced folder 590 // item to be removed. 591 if (!sync_item) 592 continue; 593 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString(); 594 change_list.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD, 595 GetSyncDataFromSyncItem(sync_item))); 596 } 597 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); 598 599 // Adding items may have created folders without setting their positions 600 // since we haven't started observing the item list yet. Resolve those. 601 ResolveFolderPositions(); 602 603 // Start observing app list model changes. 604 model_observer_.reset(new ModelObserver(this)); 605 606 return result; 607 } 608 609 void AppListSyncableService::StopSyncing(syncer::ModelType type) { 610 DCHECK_EQ(type, syncer::APP_LIST); 611 612 sync_processor_.reset(); 613 sync_error_handler_.reset(); 614 model_->SetFoldersEnabled(false); 615 } 616 617 syncer::SyncDataList AppListSyncableService::GetAllSyncData( 618 syncer::ModelType type) const { 619 DCHECK_EQ(syncer::APP_LIST, type); 620 621 VLOG(1) << this << ": GetAllSyncData: " << sync_items_.size(); 622 syncer::SyncDataList list; 623 for (SyncItemMap::const_iterator iter = sync_items_.begin(); 624 iter != sync_items_.end(); ++iter) { 625 VLOG(2) << this << " -> SYNC: " << iter->second->ToString(); 626 list.push_back(GetSyncDataFromSyncItem(iter->second)); 627 } 628 return list; 629 } 630 631 syncer::SyncError AppListSyncableService::ProcessSyncChanges( 632 const tracked_objects::Location& from_here, 633 const syncer::SyncChangeList& change_list) { 634 if (!sync_processor_.get()) { 635 return syncer::SyncError(FROM_HERE, 636 syncer::SyncError::DATATYPE_ERROR, 637 "App List syncable service is not started.", 638 syncer::APP_LIST); 639 } 640 641 // Don't observe the model while processing incoming sync changes. 642 model_observer_.reset(); 643 644 VLOG(1) << this << ": ProcessSyncChanges: " << change_list.size(); 645 for (syncer::SyncChangeList::const_iterator iter = change_list.begin(); 646 iter != change_list.end(); ++iter) { 647 const SyncChange& change = *iter; 648 VLOG(2) << this << " Change: " 649 << change.sync_data().GetSpecifics().app_list().item_id() 650 << " (" << change.change_type() << ")"; 651 if (change.change_type() == SyncChange::ACTION_ADD || 652 change.change_type() == SyncChange::ACTION_UPDATE) { 653 ProcessSyncItemSpecifics(change.sync_data().GetSpecifics().app_list()); 654 } else if (change.change_type() == SyncChange::ACTION_DELETE) { 655 DeleteSyncItemSpecifics(change.sync_data().GetSpecifics().app_list()); 656 } else { 657 LOG(ERROR) << "Invalid sync change"; 658 } 659 } 660 661 // Continue observing app list model changes. 662 model_observer_.reset(new ModelObserver(this)); 663 664 return syncer::SyncError(); 665 } 666 667 // AppListSyncableService private 668 669 bool AppListSyncableService::ProcessSyncItemSpecifics( 670 const sync_pb::AppListSpecifics& specifics) { 671 const std::string& item_id = specifics.item_id(); 672 if (item_id.empty()) { 673 LOG(ERROR) << "AppList item with empty ID"; 674 return false; 675 } 676 SyncItem* sync_item = FindSyncItem(item_id); 677 if (sync_item) { 678 // If an item of the same type exists, update it. 679 if (sync_item->item_type == specifics.item_type()) { 680 UpdateSyncItemFromSync(specifics, sync_item); 681 ProcessExistingSyncItem(sync_item); 682 VLOG(2) << this << " <- SYNC UPDATE: " << sync_item->ToString(); 683 return false; 684 } 685 // Otherwise, one of the entries should be TYPE_REMOVE_DEFAULT_APP. 686 if (sync_item->item_type != 687 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP && 688 specifics.item_type() != 689 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) { 690 LOG(ERROR) << "Synced item type: " << specifics.item_type() 691 << " != existing sync item type: " << sync_item->item_type 692 << " Deleting item from model!"; 693 model_->DeleteItem(item_id); 694 } 695 VLOG(2) << this << " - ProcessSyncItem: Delete existing entry: " 696 << sync_item->ToString(); 697 delete sync_item; 698 sync_items_.erase(item_id); 699 } 700 701 sync_item = CreateSyncItem(item_id, specifics.item_type()); 702 UpdateSyncItemFromSync(specifics, sync_item); 703 ProcessNewSyncItem(sync_item); 704 VLOG(2) << this << " <- SYNC ADD: " << sync_item->ToString(); 705 return true; 706 } 707 708 void AppListSyncableService::ProcessNewSyncItem(SyncItem* sync_item) { 709 VLOG(2) << "ProcessNewSyncItem: " << sync_item->ToString(); 710 switch (sync_item->item_type) { 711 case sync_pb::AppListSpecifics::TYPE_APP: { 712 // New apps are added through ExtensionAppModelBuilder. 713 // TODO(stevenjb): Determine how to handle app items in sync that 714 // are not installed (e.g. default / OEM apps). 715 return; 716 } 717 case sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP: { 718 VLOG(1) << this << ": Uninstall: " << sync_item->ToString(); 719 UninstallExtension(extension_system_->extension_service(), 720 sync_item->item_id); 721 return; 722 } 723 case sync_pb::AppListSpecifics::TYPE_FOLDER: { 724 AppListItem* app_item = model_->FindItem(sync_item->item_id); 725 if (!app_item) 726 return; // Don't create new folders here, the model will do that. 727 UpdateAppItemFromSyncItem(sync_item, app_item); 728 return; 729 } 730 case sync_pb::AppListSpecifics::TYPE_URL: { 731 // TODO(stevenjb): Implement 732 LOG(WARNING) << "TYPE_URL not supported"; 733 return; 734 } 735 } 736 NOTREACHED() << "Unrecognized sync item type: " << sync_item->ToString(); 737 } 738 739 void AppListSyncableService::ProcessExistingSyncItem(SyncItem* sync_item) { 740 if (sync_item->item_type == 741 sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) { 742 return; 743 } 744 VLOG(2) << "ProcessExistingSyncItem: " << sync_item->ToString(); 745 AppListItem* app_item = model_->FindItem(sync_item->item_id); 746 DVLOG(2) << " AppItem: " << app_item->ToDebugString(); 747 if (!app_item) { 748 LOG(ERROR) << "Item not found in model: " << sync_item->ToString(); 749 return; 750 } 751 // This is the only place where sync can cause an item to change folders. 752 if (app_list::switches::IsFolderUIEnabled() && 753 app_item->folder_id() != sync_item->parent_id && 754 !AppIsOem(app_item->id())) { 755 VLOG(2) << " Moving Item To Folder: " << sync_item->parent_id; 756 model_->MoveItemToFolder(app_item, sync_item->parent_id); 757 } 758 UpdateAppItemFromSyncItem(sync_item, app_item); 759 } 760 761 void AppListSyncableService::UpdateAppItemFromSyncItem( 762 const AppListSyncableService::SyncItem* sync_item, 763 AppListItem* app_item) { 764 VLOG(2) << this << " UpdateAppItemFromSyncItem: " << sync_item->ToString(); 765 if (!app_item->position().Equals(sync_item->item_ordinal)) 766 model_->SetItemPosition(app_item, sync_item->item_ordinal); 767 // Only update the item name if it is a Folder or the name is empty. 768 if (sync_item->item_name != app_item->name() && 769 sync_item->item_id != kOemFolderId && 770 (app_item->GetItemType() == AppListFolderItem::kItemType || 771 app_item->name().empty())) { 772 model_->SetItemName(app_item, sync_item->item_name); 773 } 774 } 775 776 bool AppListSyncableService::SyncStarted() { 777 if (sync_processor_.get()) 778 return true; 779 if (flare_.is_null()) { 780 VLOG(1) << this << ": SyncStarted: Flare."; 781 flare_ = sync_start_util::GetFlareForSyncableService(profile_->GetPath()); 782 flare_.Run(syncer::APP_LIST); 783 } 784 return false; 785 } 786 787 void AppListSyncableService::SendSyncChange( 788 SyncItem* sync_item, 789 SyncChange::SyncChangeType sync_change_type) { 790 if (!SyncStarted()) { 791 DVLOG(2) << this << " - SendSyncChange: SYNC NOT STARTED: " 792 << sync_item->ToString(); 793 return; 794 } 795 if (!initial_sync_data_processed_ && 796 sync_change_type == SyncChange::ACTION_ADD) { 797 // This can occur if an initial item is created before its folder item. 798 // A sync item should already exist for the folder, so we do not want to 799 // send an ADD event, since that would trigger a CHECK in the sync code. 800 DCHECK(sync_item->item_type == sync_pb::AppListSpecifics::TYPE_FOLDER); 801 DVLOG(2) << this << " - SendSyncChange: ADD before initial data processed: " 802 << sync_item->ToString(); 803 return; 804 } 805 if (sync_change_type == SyncChange::ACTION_ADD) 806 VLOG(2) << this << " -> SYNC ADD: " << sync_item->ToString(); 807 else 808 VLOG(2) << this << " -> SYNC UPDATE: " << sync_item->ToString(); 809 SyncChange sync_change(FROM_HERE, sync_change_type, 810 GetSyncDataFromSyncItem(sync_item)); 811 sync_processor_->ProcessSyncChanges( 812 FROM_HERE, syncer::SyncChangeList(1, sync_change)); 813 } 814 815 AppListSyncableService::SyncItem* 816 AppListSyncableService::FindSyncItem(const std::string& item_id) { 817 SyncItemMap::iterator iter = sync_items_.find(item_id); 818 if (iter == sync_items_.end()) 819 return NULL; 820 return iter->second; 821 } 822 823 AppListSyncableService::SyncItem* 824 AppListSyncableService::CreateSyncItem( 825 const std::string& item_id, 826 sync_pb::AppListSpecifics::AppListItemType item_type) { 827 DCHECK(!ContainsKey(sync_items_, item_id)); 828 SyncItem* sync_item = new SyncItem(item_id, item_type); 829 sync_items_[item_id] = sync_item; 830 return sync_item; 831 } 832 833 void AppListSyncableService::DeleteSyncItemSpecifics( 834 const sync_pb::AppListSpecifics& specifics) { 835 const std::string& item_id = specifics.item_id(); 836 if (item_id.empty()) { 837 LOG(ERROR) << "Delete AppList item with empty ID"; 838 return; 839 } 840 VLOG(2) << this << ": DeleteSyncItemSpecifics: " << item_id.substr(0, 8); 841 SyncItemMap::iterator iter = sync_items_.find(item_id); 842 if (iter == sync_items_.end()) 843 return; 844 sync_pb::AppListSpecifics::AppListItemType item_type = 845 iter->second->item_type; 846 VLOG(2) << this << " <- SYNC DELETE: " << iter->second->ToString(); 847 delete iter->second; 848 sync_items_.erase(iter); 849 // Only delete apps from the model. Folders will be deleted when all 850 // children have been deleted. 851 if (item_type == sync_pb::AppListSpecifics::TYPE_APP) 852 model_->DeleteItem(item_id); 853 } 854 855 std::string AppListSyncableService::FindOrCreateOemFolder() { 856 AppListFolderItem* oem_folder = model_->FindFolderItem(kOemFolderId); 857 if (!oem_folder) { 858 scoped_ptr<AppListFolderItem> new_folder(new AppListFolderItem( 859 kOemFolderId, AppListFolderItem::FOLDER_TYPE_OEM)); 860 oem_folder = static_cast<AppListFolderItem*>( 861 model_->AddItem(new_folder.PassAs<app_list::AppListItem>())); 862 SyncItem* oem_sync_item = FindSyncItem(kOemFolderId); 863 if (oem_sync_item) { 864 VLOG(1) << "Creating OEM folder from existing sync item: " 865 << oem_sync_item->item_ordinal.ToDebugString(); 866 model_->SetItemPosition(oem_folder, oem_sync_item->item_ordinal); 867 } else { 868 model_->SetItemPosition(oem_folder, GetOemFolderPos()); 869 // Do not create a sync item for the OEM folder here, do it in 870 // ResolveFolderPositions() when the item position is finalized. 871 } 872 } 873 model_->SetItemName(oem_folder, oem_folder_name_); 874 return oem_folder->id(); 875 } 876 877 syncer::StringOrdinal AppListSyncableService::GetOemFolderPos() { 878 VLOG(1) << "GetOemFolderPos: " << first_app_list_sync_; 879 if (!first_app_list_sync_) { 880 VLOG(1) << "Sync items exist, placing OEM folder at end."; 881 syncer::StringOrdinal last; 882 for (SyncItemMap::iterator iter = sync_items_.begin(); 883 iter != sync_items_.end(); ++iter) { 884 SyncItem* sync_item = iter->second; 885 if (!last.IsValid() || sync_item->item_ordinal.GreaterThan(last)) 886 last = sync_item->item_ordinal; 887 } 888 return last.CreateAfter(); 889 } 890 891 // Place the OEM folder just after the web store, which should always be 892 // followed by a pre-installed app (e.g. Search), so the poosition should be 893 // stable. TODO(stevenjb): consider explicitly setting the OEM folder location 894 // along with the name in ServicesCustomizationDocument::SetOemFolderName(). 895 AppListItemList* item_list = model_->top_level_item_list(); 896 if (item_list->item_count() == 0) 897 return syncer::StringOrdinal(); 898 899 size_t oem_index = 0; 900 for (; oem_index < item_list->item_count() - 1; ++oem_index) { 901 AppListItem* cur_item = item_list->item_at(oem_index); 902 if (cur_item->id() == extensions::kWebStoreAppId) 903 break; 904 } 905 syncer::StringOrdinal oem_ordinal; 906 AppListItem* prev = item_list->item_at(oem_index); 907 if (oem_index + 1 < item_list->item_count()) { 908 AppListItem* next = item_list->item_at(oem_index + 1); 909 oem_ordinal = prev->position().CreateBetween(next->position()); 910 } else { 911 oem_ordinal = prev->position().CreateAfter(); 912 } 913 VLOG(1) << "Placing OEM Folder at: " << oem_index 914 << " position: " << oem_ordinal.ToDebugString(); 915 return oem_ordinal; 916 } 917 918 bool AppListSyncableService::AppIsOem(const std::string& id) { 919 if (!extension_system_->extension_service()) 920 return false; 921 const extensions::Extension* extension = 922 extension_system_->extension_service()->GetExtensionById(id, true); 923 return extension && extension->was_installed_by_oem(); 924 } 925 926 std::string AppListSyncableService::SyncItem::ToString() const { 927 std::string res = item_id.substr(0, 8); 928 if (item_type == sync_pb::AppListSpecifics::TYPE_REMOVE_DEFAULT_APP) { 929 res += " { RemoveDefault }"; 930 } else { 931 res += " { " + item_name + " }"; 932 res += " [" + item_ordinal.ToDebugString() + "]"; 933 if (!parent_id.empty()) 934 res += " <" + parent_id.substr(0, 8) + ">"; 935 } 936 return res; 937 } 938 939 } // namespace app_list 940