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