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