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